13-Dec

JavaScript, React

How to create a simple Custom Server in Next.js

Do you need help with setting up a simple custom server in Next.js? This trick will get you a long way!

2 min read

·

By Toralf Frich

·

December 13, 2022

For many frameworks it is necessary to create your own server that listens and handles user interactions with your application.

In Next.js, a server is automatically generated 🤯. And, so, for most, there is no need to think about creating your own server. However, sometimes, it might be necessary with a custom server. In particular, due to two main reasons:

  1. Wanting to run some code before the Next.js application is running (a common issue)
  2. Creating custom server patterns

The reason why I wanted to create a custom server was due to reason 1. After multiple attempts, I figured that one way to do it was to replicate the server that Next.js generated when building a standalone application. This is a bit “hacky”, but it is a lot easier than many of the other alternatives! In addition, it serves as a baseline for more complicated custom servers 📈

A standalone is just a smaller version of the application you are creating which only contains the necessary parts for running the application. You can create a standalone by adding output: ‘standalone’ to your config:

/** @type {import('next').NextConfig} */
const nextConfig = {
    output: 'standalone',
};

module.exports = nextConfig;

After having built the application (with yarn build or etc.), a standalone directory will be available in the root of your project. Inside it you will find ‘server.js’ which looks something like this:

process.env.NODE_ENV = 'production'
process.chdir(__dirname)
const NextServer = require('next/dist/server/next-server').default
const http = require('http')
const path = require('path')

// Make sure commands gracefully respect termination signals (e.g. from Docker)
// Allow the graceful termination to be manually configurable
if (!process.env.NEXT_MANUAL_SIG_HANDLE) {
  process.on('SIGTERM', () => process.exit(0))
  process.on('SIGINT', () => process.exit(0))
}

let handler

const server = http.createServer(async (req, res) => {
  try {
    await handler(req, res)
  } catch (err) {
    console.error(err);
    res.statusCode = 500
    res.end('internal server error')
  }
})
const currentPort = parseInt(process.env.PORT, 10) || 3000

server.listen(currentPort, (err) => {
  if (err) {
    console.error("Failed to start server", err)
    process.exit(1)
  }
  const nextServer = new NextServer({
    hostname: 'localhost',
    port: currentPort,
    dir: path.join(__dirname),
    dev: false,
    customServer: false,
    conf: {"super-duper-long config automatically generated by Next.js"},
  })
  handler = nextServer.getRequestHandler()

  console.log("Listening on port", currentPort)
})

An easy way to create a custom server would be to copy the file above. However, we can't do this as there is a super-duper-long config that we need to get from ‘required-server-files.json' 🤔. My trick for getting this was by using fs and JSON.parse. Hence, I ended up with a server file that looked like this:

process.env.NODE_ENV = 'production';
process.chdir(__dirname);

const NextServer = require('next/dist/server/next-server').default;
const http = require('http');
const path = require('path');

const fs = require('fs');
const { config } = JSON.parse(fs.readFileSync('required-server-files.json'));

// Make sure commands gracefully respect termination signals (e.g. from Docker)
process.on('SIGTERM', () => process.exit(0));
process.on('SIGINT', () => process.exit(0));

let handler;

const server = http.createServer(async (req, res) => {
    try {
        await handler(req, res);
    } catch (err) {
        console.error(err);
        res.statusCode = 500;
        res.end('internal server error');
    }
});
const currentPort = parseInt(process.env.PORT, 10) || 3000;
 
server.listen(currentPort, (err) => {
    if (err) {
        console.error('Failed to start server', err);
        process.exit(1);
    }
    const addr = server.address();
    const nextServer = new NextServer({
        hostname: 'localhost',
        port: currentPort,
        dir: path.join(__dirname),
        dev: false,
        conf: config,
    });
    
    handler = nextServer.getRequestHandler();
});

It is important that the import path of ‘required-server-files.json’ is correct.

Tada! You now have a basic custom server that you can use and that you are free to edit it is as much as you like 👑.

Run your application with your new custom server by using the command below instead of next start:

yarn/npm build
node server.js

That was all for now! Bye 👋