How to deploy NextJS / NodeJS webserver on Azure Webapps Free plan

Are you trying to deploy a NextJS apps on Azure Webapps, everything is deploy on the server, but nothing is working ? You are probably missing some configuration files very specific to IIS.

You will need a custom server, so that IISnode can run it, yarn start will not work. So here a very basic custom server for NextJS.

<root>/server.js

1
const { createServer } = require('http');
2
const { parse } = require('url');
3
4
const next = require('next');
5
6
const dev = process.env.NODE_ENV !== 'production';
7
const hostname = process.env.NEXT_HOST_NAME || 'localhost';
8
const port = process.env.PORT || 3000;
9
// when using middleware 'hostname' and 'port' must be provided below
10
const app = next({ dev, hostname, port });
11
const handle = app.getRequestHandler();
12
13
app.prepare().then(() => {
14
createServer(async (req, res) => {
15
try {
16
// Be sure to pass 'true' as the second argument to 'url.parse'.
17
// This tells it to parse the query portion of the URL.
18
const parsedUrl = parse(req.url, true);
19
await handle(req, res, parsedUrl);
20
} catch (err) {
21
console.error('Error occurred handling', req.url, err);
22
res.statusCode = 500;
23
res.end('internal server error');
24
}
25
}).listen(port, (err) => {
26
if (err) throw err;
27
console.log(`> Ready on http://${hostname}:${port}`);
28
});
29
});

Since we are on IIS we need a web.config file. This file is very basic and can be generate by azure if you don't have it. It basically give IIS the information of which file should be start with node. You guess it our server.js

<root>/web.config

1
<?xml version="1.0" encoding="utf-8"?>
2
<!--
3
This configuration file is required if iisnode is used to run node processes behind
4
IIS or IIS Express. For more information, visit:
5
6
https://github.com/tjanczuk/iisnode/blob/master/src/samples/configuration/web.config
7
-->
8
9
<configuration>
10
<system.webServer>
11
<!-- Visit http://blogs.msdn.com/b/windowsazure/archive/2013/11/14/introduction-to-websockets-on-windows-azure-web-sites.aspx for more information on WebSocket support -->
12
<webSocket enabled="false" />
13
<handlers>
14
<!-- Indicates that the server.js file is a node.js site to be handled by the iisnode module -->
15
<add name="iisnode" path="server.js" verb="*" modules="iisnode"/>
16
</handlers>
17
<rewrite>
18
<rules>
19
<!-- Do not interfere with requests for node-inspector debugging -->
20
<rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
21
<match url="^server.js\/debug[\/]?" />
22
</rule>
23
24
<!-- First we consider whether the incoming URL matches a physical file in the /public folder -->
25
<rule name="StaticContent">
26
<action type="Rewrite" url="public{REQUEST_URI}"/>
27
</rule>
28
29
<!-- All other URLs are mapped to the node.js site entry point -->
30
<rule name="DynamicContent">
31
<conditions>
32
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True"/>
33
</conditions>
34
<action type="Rewrite" url="server.js"/>
35
</rule>
36
</rules>
37
</rewrite>
38
39
<!-- 'bin' directory has no special meaning in node.js and apps can be placed in it -->
40
<security>
41
<requestFiltering>
42
<hiddenSegments>
43
<remove segment="bin"/>
44
</hiddenSegments>
45
</requestFiltering>
46
</security>
47
48
<!-- Make sure error responses are left untouched -->
49
<httpErrors existingResponse="PassThrough" />
50
51
<!--
52
You can control how Node is hosted within IIS using the following options:
53
* watchedFiles: semi-colon separated list of files that will be watched for changes to restart the server
54
* node_env: will be propagated to node as NODE_ENV environment variable
55
* debuggingEnabled - controls whether the built-in debugger is enabled
56
57
See https://github.com/tjanczuk/iisnode/blob/master/src/samples/configuration/web.config for a full list of options
58
-->
59
<!--<iisnode watchedFiles="web.config;*.js"/>-->
60
</system.webServer>
61
</configuration>

Finally we need to tell the server to install our npm package, (sending them along the deploy is bad pratice, since some package are OS specific)

The first file tell IIS that it should execute a custom script

<root>/.deployment

1
[config]
2
command = deploy.cmd

The second file will check if we have node and npm and install our deps

<root>/deploy.cmd

1
@if "%SCM_TRACE_LEVEL%" NEQ "4" @echo off
2
3
:: ----------------------
4
:: KUDU Deployment Script
5
:: Version: 0.1.11
6
:: ----------------------
7
8
:: Prerequisites
9
:: -------------
10
11
:: Verify node.js installed
12
where node 2>nul >nul
13
IF %ERRORLEVEL% NEQ 0 (
14
echo Missing node.js executable, please install node.js, if already installed make sure it can be reached from current environment.
15
goto error
16
)
17
18
:: Setup
19
:: -----
20
21
setlocal enabledelayedexpansion
22
23
SET ARTIFACTS=%~dp0%..\\artifacts
24
25
IF NOT DEFINED DEPLOYMENT_SOURCE (
26
SET DEPLOYMENT_SOURCE=%~dp0%.
27
)
28
29
IF NOT DEFINED DEPLOYMENT_TARGET (
30
SET DEPLOYMENT_TARGET=%ARTIFACTS%\\wwwroot
31
)
32
33
IF NOT DEFINED NEXT_MANIFEST_PATH (
34
SET NEXT_MANIFEST_PATH=%ARTIFACTS%\\manifest
35
36
IF NOT DEFINED PREVIOUS_MANIFEST_PATH (
37
SET PREVIOUS_MANIFEST_PATH=%ARTIFACTS%\\manifest
38
)
39
)
40
41
IF NOT DEFINED KUDU_SYNC_CMD (
42
:: Install kudu sync
43
echo Installing Kudu Sync
44
call npm install kudusync -g --silent
45
IF !ERRORLEVEL! NEQ 0 goto error
46
47
:: Locally just running "kuduSync" would also work
48
SET KUDU_SYNC_CMD=%appdata%\\npm\\kuduSync.cmd
49
)
50
goto Deployment
51
52
:: Utility Functions
53
:: -----------------
54
55
:SelectNodeVersion
56
57
IF DEFINED KUDU_SELECT_NODE_VERSION_CMD (
58
:: The following are done only on Windows Azure Websites environment
59
call %KUDU_SELECT_NODE_VERSION_CMD% "%DEPLOYMENT_SOURCE%" "%DEPLOYMENT_TARGET%" "%DEPLOYMENT_TEMP%"
60
IF !ERRORLEVEL! NEQ 0 goto error
61
62
IF EXIST "%DEPLOYMENT_TEMP%\\__nodeVersion.tmp" (
63
SET /p NODE_EXE=<"%DEPLOYMENT_TEMP%\\__nodeVersion.tmp"
64
IF !ERRORLEVEL! NEQ 0 goto error
65
)
66
67
IF EXIST "%DEPLOYMENT_TEMP%\\__npmVersion.tmp" (
68
SET /p NPM_JS_PATH=<"%DEPLOYMENT_TEMP%\\__npmVersion.tmp"
69
IF !ERRORLEVEL! NEQ 0 goto error
70
)
71
72
IF NOT DEFINED NODE_EXE (
73
SET NODE_EXE=node
74
)
75
76
SET NPM_CMD="!NODE_EXE!" "!NPM_JS_PATH!"
77
) ELSE (
78
SET NPM_CMD=npm
79
SET NODE_EXE=node
80
)
81
82
goto :EOF
83
84
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
85
:: Deployment
86
:: ----------
87
88
:Deployment
89
echo Handling node.js deployment.
90
91
:: 1. KuduSync
92
IF /I "%IN_PLACE_DEPLOYMENT%" NEQ "1" (
93
call :ExecuteCmd "%KUDU_SYNC_CMD%" -v 50 -f "%DEPLOYMENT_SOURCE%" -t "%DEPLOYMENT_TARGET%" -n "%NEXT_MANIFEST_PATH%" -p "%PREVIOUS_MANIFEST_PATH%" -i ".git;.hg;.deployment;deploy.cmd"
94
IF !ERRORLEVEL! NEQ 0 goto error
95
)
96
97
:: 2. Select node version
98
call :SelectNodeVersion
99
100
:: 3. Install npm packages
101
IF EXIST "%DEPLOYMENT_TARGET%\\package.json" (
102
pushd "%DEPLOYMENT_TARGET%"
103
call :ExecuteCmd !NPM_CMD! install --production
104
IF !ERRORLEVEL! NEQ 0 goto error
105
popd
106
)
107
108
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
109
110
:: Post deployment stub
111
IF DEFINED POST_DEPLOYMENT_ACTION call "%POST_DEPLOYMENT_ACTION%"
112
IF !ERRORLEVEL! NEQ 0 goto error
113
114
goto end
115
116
:: Execute command routine that will echo out when error
117
:ExecuteCmd
118
setlocal
119
set _CMD_=%*
120
call %_CMD_%
121
if "%ERRORLEVEL%" NEQ "0" echo Failed exitCode=%ERRORLEVEL%, command=%_CMD_%
122
exit /b %ERRORLEVEL%
123
124
:error
125
endlocal
126
echo An error has occurred during web site deployment.
127
call :exitSetErrorLevel
128
call :exitFromFunction 2>nul
129
130
:exitSetErrorLevel
131
exit /b 1
132
133
:exitFromFunction
134
()
135
136
:end
137
endlocal
138
echo Finished successfully.

With these file add to your project you should now be able to have your NextJS website working on Azure Webapps Free plan