DEV Community

Cover image for NextJS Tip: Hot reloading for dynamic servers
Ryosuke
Ryosuke

Posted on

NextJS Tip: Hot reloading for dynamic servers

Have you been developing a NextJS app with dynamic routing (using maybe Express), and found that every time you make a change you have to do the tedious process of shutting down the server (CTRL+C) and restarting it? (npm run dev).

If you're used to working with NodeJS, or ExpressJS, you've probably come across nodemon. It's a utility that enables hot reloading on Node-based servers, so that whenever you make a change to a server file and save -- it instantly starts to restart without any prompt from your part.

But nodemon doesn't work out of the box with NextJS and requires a small amount of configuration. If you try running nodemon without a config or the proper CLI params, you'll find that your server will start acting real wonky. My server started restarting infinitely, because it was detecting changes each time NextJS compiled, triggering an infinite loop of compilations.

This guide assumes you have a NextJS project with dynamic routing setup. You can find a few in the examples section of the NextJS repo

The solution?

Nodemon accepts a configuration file, which allows you have a greater degree of control over the process. By adding a few values to this file, we can solve all our issues.

Install nodemon

If you haven't already, install nodemon:

npm install --save-dev nodemon

Create the config file

Create a nodemon.json file in the project root and paste the following into it:

{
    "verbose": true,
    "ignore": ["node_modules", ".next"],
    "watch": ["server/**/*", "server.js"],
    "ext": "js json"
}
Enter fullscreen mode Exit fullscreen mode

This tells nodemon to ignore the .next folder, which is used as a cache for the Next compiler (and triggers the infinite reload). And we also tell it which file to watch for changes from. I keep my server file in a separate server folder, since I have stuff like routes/middleware/etc that need separate files and folders.

Update your npm dev script

Now you can modify your package.json and update the 'dev' script value to use nodemon instead of the default node server.js:

  "scripts": {
    "dev": "nodemon -w server/server.js server/server.js",
    "build": "next build",
    "start": "NODE_ENV=production node server.js"
  },
Enter fullscreen mode Exit fullscreen mode

Now you can run npm run dev and you'll have yourself a hot-reloading server.

I found this solution on the NextJS Github issues, where a people were having - go figure - the same issue.

Hope that helps ✌️
Ryo


References:

Top comments (8)

Collapse
 
jelugbadebo profile image
jelugbadebo

I am getting a ECONNREFUSED ::1:64146 error when trying to spin up my app with nodemon script:

"dev": "nodemon -w server/server.js server/server.js",
Enter fullscreen mode Exit fullscreen mode

When I spin up my app with Next script, the app spins up fine. Here is the script:

"dev": "next dev",
Enter fullscreen mode Exit fullscreen mode

Here is my nodemon.json:

{
    "restartable": "false",
    "colours": true,
    "ignore": [".git", "node_modules", ".next"],
    "runOnChangeOnly": false,
    "verbose": true,
    "events": {
        "restart": "osascript -e 'display notification \"App restarted due to:\n'$FILENAME'\" with title \"nodemon\"'"
    },
    "watch": ["components/**/*", "redux/**/*", "app/**/*", "server/**/*"],
    "env": {
        "NODE_ENV": "development"
    },
    "ext": "js json"
}
Enter fullscreen mode Exit fullscreen mode

Here is my server.js:

import * as dotenv from 'dotenv';
dotenv.config({ debug: process.env.DEBUG });

import express from 'express';
import next from 'next';
import cors from 'cors';
import morgan from 'morgan';

// Configure Next App
const port = process.env.PORT || 5000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({
    dir: '.',
    dev: dev,
});
// Handle provides response to request made to front end of app
const handle = app.getRequestHandler();

// Import API Routes
import { authRouter } from './routes/authRoutes.js';

// import { postRouter } from './routes/postRoutes.js';
// Import Database Connect
import connectDB from './db/connect.js';

app
    .prepare()
    .then(() => {
        // Configure Express Backend
        const server = express();

        const corsOptions = {
            origin: '*',
            methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
            preflightContinue: false,
            optionsSuccessStatus: 204, // some legacy browsers (IE11, various SmartTVs) choke on 204
        };

        // Implement Middleware
        server.use(cors(corsOptions));
        server.use(express.json());
        server.use(express.urlencoded({ extended: true }));
        // Invoke morgan logger middleware only in dev environment
        if (process.env.NODE_ENV !== 'production') {
            server.use(morgan('dev'));
        }

        // Establish Backend Routes
        server.use('/api/v1/auth', authRouter);
        // server.use('/api/v1/post', postRouter);

        // Establish Frontend Routes, '*' wildcard is route for frontend
        server.get('*', (req, res) => {
            return handle(req, res);
        });

        // Connect to Database
        // const connectDB = function (url) {
        //  return mongoose.set('strictQuery', true).connect(url);
        // };

        // Start Server
        const startServer = async function () {
            try {
                await connectDB(process.env.MONGO_URL);
                console.log(`SERVER is Connected to DATABASE...`);
                server.listen(port, () => {
                    console.log(`SERVER is Listening on PORT ${port}...`);
                });
            } catch (error) {
                console.error(error);
            }
        };

        startServer();
    })
    .catch((error) => {
        console.log(error);
        process.exit(1);
    });
Enter fullscreen mode Exit fullscreen mode

Here is my next.config:

const nextConfig = {
    reactStrictMode: true,
    eslint: {
        ignoreDuringBuilds: true,
    },
    async rewrites() {
        return [
            {
                source: '/api/:slug*',
                destination: 'http://127.0.0.1:3000/api/:slug*',
            },
        ];
    },
};

export default nextConfig;
Enter fullscreen mode Exit fullscreen mode

Here is the Error I'm getting when using nodemon:

Error: connect ECONNREFUSED ::1:64146
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1494:16) {
  errno: -4078,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '::1',
  port: 64146
}
GET / 500 25.020 ms - -
Enter fullscreen mode Exit fullscreen mode

Here is my package.json:

{
    "name": "practice12-next-mern-todo",
    "version": "0.1.0",
    "type": "module",
    "private": true,
    "engines": {
        "node": "18.16.0",
        "npm": "9.7.1"
    },
    "scripts": {
        "dev": "nodemon -w server/server.js server/server.js",
        "build": "next build",
        "start": "node server/server.js",
        "lint": "next lint"
    },
    "dependencies": {
        "@reduxjs/toolkit": "^1.9.5",
        "cors": "^2.8.5",
        "dotenv": "^16.3.1",
        "eslint": "8.43.0",
        "eslint-config-next": "13.4.7",
        "express": "^4.18.2",
        "http-status-codes": "^2.2.0",
        "mongoose": "^7.3.1",
        "morgan": "^1.10.0",
        "next": "13.4.7",
        "nodemon": "^2.0.21",
        "react": "18.2.0",
        "react-dom": "18.2.0",
        "react-redux": "^8.1.1"
    }
}
Enter fullscreen mode Exit fullscreen mode

Any thoughts on what the issue is?

Collapse
 
ebsalberto profile image
Eduardo Alberto

Good one.

Quick correction: you shouldn't use -w on your package.json or it'll ignore your nodemon.json config.

Collapse
 
peacefulseeker profile image
Alexey Vorobyov

Yeah, should not it be just nodemon server.js to start up the main server file and it will trigger server reload
in case any of specified files in nodemon.json were altered?

Collapse
 
_aquasar profile image
Alex Quasar

Okay great, this one is pretty standard. How to refresh the browser when you make changes though using Next/ SSR?

Collapse
 
sheptang profile image
Anıl Yarkın Yücel

I'd do Ctrl+F5

Collapse
 
deleonjulio profile image
deleonjulio

Wow, thank you so much!

Collapse
 
rafaelcg profile image
Rafael Corrêa Gomes

Thanks for sharing!

Collapse
 
francisrod01 profile image
Francis Rodrigues

I know nodemon since my first try with node.js. That's good you're sharing it with JavaScript beginners :)