A Step-by-Step Guide to Deploying Next.js Web Applications with PM2
How to write Next.js environment variables deployment scripts in several stages of development into production with PM2 process manager node clusters
Web StoryPrequisite
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
orpm2 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.