A Step-by-Step Guide to Deploying Next.js Web Applications with PM2

November 19, 2022 Dykraf

How to write Next.js environment variables deployment scripts in several stages of development into production with PM2 process manager node clusters

Web Story

Prequisite

Next.js is a web framework built on top of React.js and as a Server Side Rendering, Static Site Generator, and basically you can use it as a Full stack web framework. If you are using Next.js, the command script is already built-in into the package.json script so developers can execute the command for development, build, and start the production build.

Next.js development script:

yarn dev

Next.js build script:

yarn build

Next.js start production build script:

yarn start

Next.js takes the environment variables value from .env or .env.* files and environment variables must be referenced as e.g. process.env.APP_ENV, not const { APP_ENV } = process.env because it is running in a Node.js runtime.

There is also another way to get environment variables in Next.js and that is exposing environment variables to the client browser transformed at build time. You can define the NEXT_PUBLIC_ prefix in each of your env variables such as NEXT_PUBLIC_My_Env_Var variables on those .env files. Write process.env.NEXT_PUBLIC_My_Env_Var To get the variables in Next.js pages and components in order for you to use them in the project.

We might want to use default env variables from Node.js such as NODE_ENV, but the allowed values for NODE_ENV are production, development, and test. Next.js allows you to set defaults in .env (all environments), .env.development (development environment), and .env.production (production environment). .env.local always overrides the defaults set.

Next.js recommended that if we want to use other names than those NODE_ENV variables, we could use APP_ENV such as APP_ENV=local, APP_ENV=dev or APP_ENV=prod in the environment variables.

Why PM2 (Process Manager) for deployment?

Starting a Next.js web app project for basic requirements seems good enough from the built-in command on the package.json file. But when the web app project needs to scale up, the environment, configuration, and structure need to scale up too.

Some adjustments are needed for scaling up the system. Environment, configuration, and structure must be separately defined in the Next.js web application project.

PM2 is an advance, production process manager for Node.js. Node cluster management, logging, and memory management are the key features of PM2. PM2 is a daemon process manager that will help you manage and keep your application online 24/7. PM2 for env and configuration deployment can also be included in the Next.js web app as they run on Node.js ecosystem.

There are several ways to include PM2 in Next.js production deployment scripts.

Getting Started

Make sure you have PM2 installed on your local machine or on your servers:

npm install pm2 -g

Run and test PM2 installed and working properly:

pm2 ls

Run and test PM2 ls command

Using PM2 default CLI scaffold

Using the PM2 command to initiate the deployment script: $ pm2 init simple and will initiate an ecosystem.config.js sample file. This is the main PM2 config used for Next.js deployment script in their documentation website.

module.exports = {
  apps: [
    {
      name: 'NextAppName',
      exec_mode: 'cluster',
      instances: 'max', // Or a number of instances
      script: 'node_modules/next/dist/bin/next',
      args: 'start',
      env_local: {
        APP_ENV: 'local' // APP_ENV=local
      },
      env_dev: {
        APP_ENV: 'dev' // APP_ENV=dev
      },
      env_prod: {
        APP_ENV: 'prod' // APP_ENV=prod
      }
    }
  ]
}

Running the PM2 ecosystem config file for different environments $ pm2 startOrRestart ecosystem.config.js --env local, $ pm2 startOrRestart ecosystem.config.js --env dev or $ pm2 startOrRestart ecosystem.config.js --env prod.

Running startOrRestart is a command for PM2 to start running the daemon instances if not started or restart if already have instances running in the background. But you can use simply use the start because it will override the running instances and replace them with the new build scripts.

To start this ecosystem.config.js just simply type pm2 start ecosystem.config.js --env prod depends on your environment deployment. For more simplyfy command, you can add this into your package.json scripts.

  ...
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "deploy:local": "next build && pm2 startOrRestart ecosystem.config.js --env local"
    "deploy:dev": "next build && pm2 startOrRestart ecosystem.config.js --env dev"
    "deploy:prod": "next build && pm2 startOrRestart ecosystem.config.js --env prod"
  },
  ...

Running environment deployment from this package.json just simply type the yarn deploy:local for local, yarn deploy:dev for development, and yarn deploy:prod for prouction.

Using Custom PM2 config

These options are used for more dynamic PM2 configuration for a framework that needs a different environment and setup. In package.json the deployment script will set the environment setup using only one file of PM2 config. Below is the sample from Next.js web app package.json deployment script.

{
  "scripts": {
    "dev": "next dev",
    "start": "next start",
    "build:local": "APP_ENV=local next build",
    "build:dev": "APP_ENV=dev next build",
    "build:prod": "APP_ENV=prod next build",
    "deploy:local": "yarn build:local && pm2 start pm2.config.js --env local",
    "deploy:dev": "yarn build:dev && pm2 start pm2.config.js --env dev",
    "deploy:prod": "yarn build:prod && pm2 start pm2.config.js --env prod"
  }
}

And this is the PM2 configuration file that will take each different environment variables for the build deployment. The arguments will takes different sets of environment variables on PM2 setup.

const argEnvIndex = process.argv.indexOf('--env')
let argEnv = (argEnvIndex !== -1 && process.argv[argEnvIndex + 1]) || ''

const RUN_ENV_MAP = {
  local: {
    instances: 2,
    max_memory_restart: '250M'
  },
  dev: {
    instances: 2,
    max_memory_restart: '250M'
  },
  prod: {
    instances: 4,
    max_memory_restart: '1000M'
  }
}

if (!(argEnv in RUN_ENV_MAP)) {
  argEnv = 'prod'
}

module.exports = {
  apps: [
    {
      name: 'you-nextjs-appname',
      script: 'node_modules/next/dist/bin/next',
      args: 'start',
      instances: RUN_ENV_MAP[argEnv].instances,
      exec_mode: 'cluster',
      watch: false,
      max_memory_restart: RUN_ENV_MAP[argEnv].max_memory_restart,
      env_local: {
        APP_ENV: 'local'
      },
      env_dev: {
        APP_ENV: 'dev'
      },
      env_prod: {
        APP_ENV: 'prod'
      }
    }
  ]
}

When running yarn deploy:prod in the terminal, PM2 will execute the deployment and run the scripts command with the configuration and how many clusters run in the production mode.

Run and production deployment in PM2 command

If you want to remove or kill the PM2 daemons, you can execute pm2 kill, be careful using this command. You might just want to use pm2 stop instance or pm2 delete instance to stop or delete pm2 instances.

Run kill all instances deployment in PM2 command

If you already have PM2 running in the background and you execute yarn deploy:prod it will replace the running instances from the PM2 and will use the new build mode.

Run deployment script for production in PM2 command

Below is the sample if you want to use default environment variables from Next.js using NODE_ENV on PM2, and as I write before that Next.js environment variables on Node.js allowed only values for NODE_ENV are production, development, and test or you will get a Non-Standard NODE_ENV warning. You can use it like the example below:

module.exports = {
  apps: {
    name: 'your-nextjs-appname',
    script: 'node_modules/next/dist/bin/next',
    args: 'start',
    instances: 1,
    exec_mode: 'cluster',
    autorestart: true,
    watch: false,
    max_memory_restart: '1G',
    env_prod: {
      NODE_ENV: 'production' // NODE_ENV: 'development' || NODE_ENV: 'test'
    }
  }
}

If, in some cases, you use NextAuth.js or other Node.js modules and are not utilizing a .env file when deploying with PM2, you likely want to pass the NEXTAUTH_URL and NEXTAUTH_SECRET options to the Node.js runtime using PM2. You can set these environment variables in the env_ sections as follows:

module.exports = {
  apps: [
    {
      name: 'your-nextjs-appname',
      exec_mode: 'cluster',
      instances: 4, // Or a number of instances
      script: 'node_modules/next/dist/bin/next',
      args: 'start',
      env_local: {
        APP_ENV: 'local', // APP_ENV=local
        NEXTAUTH_SECRET: 'nuMEdbtcieujVcNXvBGCDw==',
        NEXTAUTH_URL: 'http://localhost:3000'
      },
      env_dev: {
        APP_ENV: 'dev', // APP_ENV=dev
        NEXTAUTH_SECRET: 'z1kYCuLD74EiD8OTBhWAHQ==',
        NEXTAUTH_URL: 'https://dev.dykraf.com'
      },
      env_prod: {
        APP_ENV: 'prod', // APP_ENV=prod
        NEXTAUTH_SECRET: 'Uf4Na5/eQOsrxy9BNw5tHg==',
        NEXTAUTH_URL: 'https://dykraf.com'
      }
    }
  ]
}

You can use PM2 with any Node.js web application framework, including Nuxt.js. There is one more way for the deployment script that will take each environment variables setup. Below is the sample from Nuxt.js that you can make each PM2 configuration file and add the deployment script in package.json such as pm2.local.config.js, pm2.dev.config.js, and pm2.production.config.js.

PM2 deployment script pm2.local.config.js file:

module.exports = {
  apps: {
    name: 'your-nuxtjs-appname',
    script: './node_modules/nuxt/bin/nuxt.js',
    instances: 2,
    exec_mode: 'cluster',
    autorestart: true,
    watch: false,
    max_memory_restart: '1G',
    env_local: {
      APP_ENV: 'local'
    }
  }
}

PM2 deployment script pm2.dev.config.js file:

module.exports = {
  apps: {
    name: 'your-nuxtjs-appname',
    script: './node_modules/nuxt/bin/nuxt.js',
    instances: 2,
    exec_mode: 'cluster',
    autorestart: true,
    watch: false,
    max_memory_restart: '1G',
    env_dev: {
      APP_ENV: 'dev'
    }
  }
}

PM2 deployment script pm2.prod.config.js file:

module.exports = {
  apps: {
    name: 'your-nuxtjs-appname',
    script: './node_modules/nuxt/bin/nuxt.js',
    instances: 4,
    exec_mode: 'cluster',
    autorestart: true,
    watch: false,
    max_memory_restart: '1G',
    env_prod: {
      APP_ENV: 'prod'
    }
  }
}

PM2 deployment script in package.json file:

{
    "scripts": {
        "dev": "nuxt",
        "nuxt": "nuxt",
        "build": "nuxt build",
        "start": "nuxt start -p 8020",
        "generate": "nuxt generate",
        "build:local": "cross-env-shell APP_ENV=local nuxt build",
        "build:dev": "cross-env-shell APP_ENV=dev nuxt build",
        "build:prod": "cross-env-shell APP_ENV=prod nuxt build",
        "deploy:local": "yarn build:local && pm2 startOrRestart pm2.local.config.js --env local",
        "deploy:dev": "yarn build:dev && pm2 startOrRestart pm2.dev.config.js --env dev",
        "deploy:prod": "yarn build:prod && pm2 startOrRestart pm2.prod.config.js --env prod",
    }
}

What is the purpose of the cross-env-shell command? It's a Node.js package designed for running commands seamlessly across different operating systems in a shell or terminal. This tool is particularly useful for both Windows and Unix-based systems, allowing you to execute Node.js runtime commands. Notably, there's a distinction in how Windows and POSIX commands handle environment variables. In POSIX, you use $APP_ENV, while on Windows, you use %APP_ENV%.

Using PM2 CLI Commands

PM2 provides a range of useful commands to efficiently manage your web application instances.

  • Logging: You can retrieve logs from your web application instances running on PM2 by simply entering pm2 log or pm2 logs in your shell.

  • Monitoring: Keep a vigilant watch over your web application instances by typing pm2 monit in your shell.

  • System StartUp: To ensure that PM2 launches automatically during system startup, enter pm2 startup and follow the setup script.

  • Instances Restarting: Subsequently, you can secure your active PM2 instances by using pm2 save to ensure they restart automatically during system startup.

There are several ways to deploy your Nextjs website, you could use you own server, any hosting service provider or use free static website hosting like Vercel and Netlify.

I have been using these hosting service provider for quite some time for publish several of my web projects. As a web developer, these hosting providers have been extremely helpful in terms of hosting my static websites.

If you want to host your Next.js web application with a PostgreSQL database on Vercel, you can read our blog about it here.

If you are using other RDBMS or NoSQL databases, such as MySQL or MongoDB, you can explore our other blog posts on connecting these servers from Docker containers. Read about how to connect MySQL server from a Docker container, as well as MongoDB, here.

Hopes that this will give you a clear explanation of how to make a deployment script with PM2 on different environment also takes some arguments on package.json and use it to set up the PM2 deployment configuration set up in your web development project.

Topics

Recent Blog List Content:

Archive