Skip to main content
Back to Category

Next.js and React Deploy: Production on VPS

Guide to deploying Next.js and React applications on a VPS. Node.js setup, Next.js build and start, PM2 process manager, Nginx reverse proxy, environment variables, and CI/CD basics.

Read time: 16 min Web Hosting / Applications
nextjsreactnodejspm2nginxdeployvpsjavascriptci-cd
Author
REXE Teknoloji Network & Security Team
Editor
REXE Teknoloji Technical Editorial
First published
Last updated

Next.js and React Deploy: Production Environment on VPS

Next.js is the most popular framework for modern React-based web applications. Running Next.js on your own VPS instead of platforms like Vercel offers cost savings, full control, and customization. This guide walks you through deploying a Next.js application to a VPS step by step.

Node.js Installation (with nvm)

hljs bash
# Install nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc

# Install LTS version
nvm install --lts
nvm use --lts
nvm alias default node

# Check versions
node --version
npm --version

Transferring the Application to the Server

hljs bash
# Create application directory
sudo mkdir -p /var/www/mynextapp
sudo chown $USER:$USER /var/www/mynextapp

# Clone the repo
cd /var/www
git clone https://github.com/user/mynextapp.git
cd mynextapp

# Install dependencies
npm install

Environment Variables

hljs bash
# Create .env.local file
nano /var/www/mynextapp/.env.local
hljs env
NODE_ENV=production
NEXT_PUBLIC_API_URL=https://api.example.com
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
NEXTAUTH_SECRET=your-secret-key-here
NEXTAUTH_URL=https://example.com

Variables prefixed with NEXT_PUBLIC_ are visible in the browser. Do not use this prefix for sensitive information (API keys, database passwords).

Next.js Build and Standalone Output

hljs javascript
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
  images: {
    domains: ['example.com'],
  },
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          { key: 'X-Frame-Options', value: 'DENY' },
          { key: 'X-Content-Type-Options', value: 'nosniff' },
          { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
        ],
      },
    ];
  },
};

module.exports = nextConfig;
hljs bash
# Create production build
npm run build

# Test the build
npm start

Process Management with PM2

hljs bash
# Install PM2
npm install -g pm2

# Create ecosystem.config.js
nano /var/www/mynextapp/ecosystem.config.js
hljs javascript
module.exports = {
  apps: [
    {
      name: 'mynextapp',
      script: '.next/standalone/server.js',
      cwd: '/var/www/mynextapp',
      instances: 'max',
      exec_mode: 'cluster',
      env: {
        NODE_ENV: 'production',
        PORT: 3000,
        HOSTNAME: '0.0.0.0'
      },
      max_memory_restart: '1G',
      autorestart: true,
      watch: false
    }
  ]
};
hljs bash
# Copy static files for standalone build
cp -r .next/static .next/standalone/.next/static
cp -r public .next/standalone/public

# Start the application
pm2 start ecosystem.config.js

# Auto-start on system boot
pm2 startup
pm2 save

# Check status
pm2 status
pm2 logs mynextapp

Nginx Reverse Proxy Configuration

hljs bash
sudo apt install nginx -y
sudo nano /etc/nginx/sites-available/mynextapp
hljs nginx
server {
    listen 80;
    server_name example.com www.example.com;

    # Cache for Next.js static files
    location /_next/static/ {
        alias /var/www/mynextapp/.next/static/;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # Public folder
    location /public/ {
        alias /var/www/mynextapp/public/;
        expires 30d;
        add_header Cache-Control "public";
    }

    # Proxy to Next.js application
    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
        proxy_read_timeout 86400;
    }

    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml;
    gzip_min_length 1000;

    client_max_body_size 50M;
}
hljs bash
sudo ln -s /etc/nginx/sites-available/mynextapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

SSL Certificate

hljs bash
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d example.com -d www.example.com

CI/CD Basics (GitHub Actions)

hljs yaml
# .github/workflows/deploy.yml
name: Deploy to VPS

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /var/www/mynextapp
            git pull origin main
            npm install
            npm run build
            cp -r .next/static .next/standalone/.next/static
            cp -r public .next/standalone/public
            pm2 reload mynextapp

Add SERVER_HOST, SERVER_USER, and SSH_PRIVATE_KEY to GitHub Actions secrets. Add the SSH key to the server's authorized_keys file.

Manual Deployment Workflow

hljs bash
cd /var/www/mynextapp
git pull origin main
npm install
npm run build
cp -r .next/static .next/standalone/.next/static
cp -r public .next/standalone/public
pm2 reload mynextapp
pm2 logs mynextapp --lines 50

On REXE servers, at least 1 GB RAM is recommended for Next.js. Large applications may require 2 GB+ RAM during build. Use PM2 cluster mode to fully utilize CPU cores.

Conclusion

Your Next.js application is now running in a production environment on a VPS, managed by PM2, behind an Nginx reverse proxy, and secured with SSL. Set up a CI/CD pipeline with GitHub Actions to enable automatic deployment on every push.

Frequently Asked Questions

I'm getting an 'out of memory' error during Next.js build.

Increase the Node.js memory limit during build: 'NODE_OPTIONS="--max-old-space-size=4096" npm run build'. Make sure your server has at least 2 GB RAM. Using standalone output in large projects reduces build time and memory usage.

Next.js Image Optimization isn't working.

Check the images.domains or images.remotePatterns setting in next.config.js. Verify that the /_next/image/ path in Nginx has the correct proxy configuration. Make sure the sharp package is installed: 'npm install sharp'.

API routes aren't working, getting 404 errors.

Make sure the /api/ path in Nginx is proxied to the Next.js application. If using standalone build, check that server.js is running correctly. Review PM2 logs with 'pm2 logs mynextapp'.

My Next.js application is slow, how do I optimize it?

Use standalone output (output: 'standalone' in next.config.js). Enable PM2 cluster mode. Add long-term cache for /_next/static/ in Nginx. Use next/image for image optimization. Identify large packages with bundle analyzer: 'npm install @next/bundle-analyzer'.

Related Articles

Deploy Node.js App: PM2 and Nginx Guide

Complete guide to deploying Node.js applications on a VPS. Node.js installation with nvm, PM2 process manager, Nginx reverse proxy, SSL certificate, and environment variables management.

15 min
nodejspm2nginx

Deploy PHP App: LAMP and LEMP Stack Guide

Complete guide to deploying PHP applications on a VPS. LAMP vs LEMP comparison, Apache/Nginx + PHP-FPM + MySQL setup, virtual host configuration, and PHP version management.

18 min
phplamplemp
Network TrafficInbound GbpsOutbound Gbps