Gotchas getting Next.js to run in Azure App Services

Posted on Updated on

I’ve recently been getting to grips with Next.js which is awesome. However, we’re a bit unusual in that we’re trying to get the resulting application to run in Microsoft Azure. Since Azure is perfectly capable of getting node applications to run under IIS using the module iisnode I thought this would be a piece of cake.

Not so.

First of all, I had a perfectly working Next.js app that was running fine on my machine, but would fail when uploaded to Azure’s app service. That’s not that unusual, but what was extremely unhelpful is that there was no error message. All that was written to the error output was:

(node:22980) [DEP0005] DeprecationWarning: Buffer() is deprecated due to security and usability issues. Please use the Buffer.alloc(), Buffer.allocUnsafe(), or Buffer.from() methods instead.

This doesn’t seem like it’s really the issue but, if it’s not, then that leaves us with nothing to go on. After a fair amount of changing settings, adding logging and, frankly, throwing spaghetti at the wall, I decided it was time to reproduce the error locally. I installed iisnode on my machine (which turned out to be a doddle) and then got my site running… or not running.

I was getting the same error on my machine: IIS would show The page cannot be displayed because an internal server error has occurred. and that was it.

What was followed was more logging, fiddling, and spaghetti-throwing. Next I tried making use of Express instead of the regular node server. Nothing worked.

A bit of background here: normally with Next.js you’d run it using simply npm start but iisnode runs a node process and passes it your server.js file. So, I had to create one. I copied a basic server.js from Zeit’s own GitHub repo (Zeit is the maker of Next.js)

By now I was starting to lose patience with this. I was into my second day of trying to get this thing to work and was starting to wonder if it was just not possible. I got my colleague involved who also checked out the solution and we banged our heads together.

Logging. Fiddling. Spaghetti.

We were now reading a lot about the internals of iisnode, and in the process my colleague learned about how iisnode starts up, and then communicates with, its node process. Googling the HTTP substatus code of 1001 brought us to where we found out that if communication failed between iisnode and the node process, a messageless error would be returned on every request.

A ha! This was starting to look likely. But why could the node process not be reached? Could it be timing out? It certainly seemed consistent, but unfortunately increasing the various delays and timeouts yielded no results; all that happened is that IIS took longer to return the error.

Buried in a GitHub issue somewhere, someone pointed out that this very problem would happen if the node server was configured to listen on an incorrect port. But why would this be happening to us, we thought? We can clearly see from our miniscule server.js that the port is coming from the correct environment variable (with a fallback of 3000 for development):

const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')

const port = parseInt(process.env.PORT, 10) || 3000
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()

app.prepare().then(() => {
createServer((req, res) => {
handle(req, res)
}).listen(port, err => {
if (err) throw err
console.log(`> Ready on http://localhost:${port}`)

This code is so simple, how can there possibly be an error here? Besides, we’d rewritten, recreated and refactored this so many times this couldn’t possibly be the problem. After all, we’d even copied it directly from Zeit themselves.

Still, we decided to log out the port number. What we saw blew out minds:

PORT: \\.\pipe\d226d7b0-64a0-4d04-96d4-a75e1278b7a9

Umm… that’s not a port number. Turns out that’s a named pipe, and it’s how iisnode communicates with the node process in Windows. So, it turned out that the source of our problem was this line:

const port = parseInt(process.env.PORT, 10) || 3000

Zeit (who obviously and reasonably have never run their application behind IIS node) were validating that the port given in the environment variable was an integer. The port coming from iisnode was certainly not an integer, so parseInt was returning NaN (which is falsey in Javascript) and therefore the port was reverting to the default: 3000. Thus iisnode was not able to communicate with the node process.

In the end the fix was merely to change the offending line to:

const port = process.env.PORT || 3000

and everything worked.

Needless to say, we literally cheered. Turned a few heads in our office.

This is one of those things that we have to deal with as programmers; in the end I don’t even mind that such a small mistake cost around two days of my time and half a day of my colleague’s time as the euphoria we felt when we saw the application running in Azure made it all worthwhile.

So, the moral of the story is: well, I’m not really sure what to take away from this one. I guess it’s that you shouldn’t validate inputs that you don’t really need to!

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s