Next.js Application Deployment and Optimization

Next.js is a React framework enabling production-grade web applications with built-in optimization, API routes, image optimization, and static generation. Deploying Next.js applications requires Node.js runtime, PM2 process manager, Nginx reverse proxy, proper caching strategies, and performance optimization. This guide covers production-ready Next.js deployment including Node.js setup, application building, PM2 service management, Nginx configuration, environment variable management, build optimization, caching strategies, and monitoring for Linux servers.

Table of Contents

Next.js Architecture Overview

Next.js applications combine React components with server-side rendering, static generation, and API routes for full-stack web development.

Architecture features:

  • React for component-based UI
  • Server-side rendering (SSR) for SEO and performance
  • Static generation (SSG) for fast static content
  • Incremental Static Regeneration (ISR) for dynamic updates
  • API routes for backend functionality
  • Image optimization for fast delivery
  • Built-in CSS and code splitting

Request flow:

  1. Nginx receives HTTP request
  2. Nginx checks for static/cached content
  3. If cached, serve directly (very fast)
  4. If dynamic, forward to Node.js via upstream
  5. Next.js server processes request
  6. React components rendered on server
  7. HTML and data returned to Nginx
  8. Nginx caches result
  9. Response sent to client

Node.js Environment

Install and configure Node.js runtime.

Update system:

sudo apt update
sudo apt upgrade -y
sudo apt install curl wget git zip unzip vim htop build-essential -y

Install Node.js 18 LTS:

# Add NodeSource repository
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -

# Install Node.js and npm
sudo apt install nodejs -y

# Verify
node --version
npm --version

Or use nvm (Node Version Manager):

# Download nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

# Load nvm
source ~/.bashrc

# Install Node.js
nvm install 18

# Verify
node --version

Create application user:

sudo useradd -m -s /bin/bash nextjs
sudo usermod -aG www-data nextjs

# Create application directory
sudo mkdir -p /home/nextjs/app
sudo chown -R nextjs:www-data /home/nextjs/app

Install PM2 globally:

sudo npm install -g pm2

# Verify
pm2 --version

Install Nginx:

sudo apt install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginx

Next.js Application Setup

Install and configure Next.js application.

Clone or create Next.js project:

cd /home/nextjs/app

# Clone from repository
sudo -u nextjs git clone https://github.com/yourname/nextjs-app.git .

# Or create new project
sudo -u nextjs npx create-next-app@latest . --typescript --eslint

Install dependencies:

cd /home/nextjs/app

# Install packages
sudo -u nextjs npm install

# Verify
ls -la node_modules

Create .env.production file:

sudo -u nextjs cat > /home/nextjs/app/.env.production << 'EOF'
NEXT_PUBLIC_API_URL=https://example.com/api
NEXT_PUBLIC_ENVIRONMENT=production
DATABASE_URL=postgresql://user:password@localhost/nextjs_db
REDIS_URL=redis://127.0.0.1:6379/0
EOF

Create next.config.js:

sudo -u nextjs cat > /home/nextjs/app/next.config.js << 'EOF'
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
  
  // Compression
  compress: true,
  
  // Image optimization
  images: {
    formats: ['image/avif', 'image/webp'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
    imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
  },

  // Headers for security and caching
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          {
            key: 'Strict-Transport-Security',
            value: 'max-age=31536000; includeSubDomains; preload'
          },
          {
            key: 'X-Content-Type-Options',
            value: 'nosniff'
          },
          {
            key: 'X-Frame-Options',
            value: 'SAMEORIGIN'
          }
        ]
      },
      {
        source: '/static/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, max-age=31536000, immutable'
          }
        ]
      }
    ]
  },

  // Redirect HTTP to HTTPS
  async redirects() {
    return []
  },

  // Rewrites for API proxy
  async rewrites() {
    return {
      beforeFiles: [],
      afterFiles: [],
      fallback: []
    }
  }
}

module.exports = nextConfig
EOF

Build Optimization

Build Next.js for production.

Build application:

cd /home/nextjs/app

# Create optimized production build
sudo -u nextjs npm run build

# Output shows: collecting page data, finalizing pages, etc.
# Verify .next directory created
ls -la .next/

Analyze build output:

# Install bundle analyzer
sudo -u nextjs npm install --save-dev @next/bundle-analyzer

# Configure in next.config.js:
# const withBundleAnalyzer = require('@next/bundle-analyzer')({
#   enabled: process.env.ANALYZE === 'true',
# })
# module.exports = withBundleAnalyzer(nextConfig)

# Run analysis
cd /home/nextjs/app
ANALYZE=true npm run build

Verify production build:

# Test standalone build locally
cd /home/nextjs/app/.next/standalone
node server.js

# Should start server on port 3000
# Test: curl http://localhost:3000/

PM2 Process Management

Configure PM2 to manage Next.js process.

Create PM2 configuration:

sudo -u nextjs cat > /home/nextjs/app/ecosystem.config.js << 'EOF'
module.exports = {
  apps: [
    {
      name: 'nextjs-app',
      script: 'node_modules/.bin/next',
      args: 'start',
      instances: 4,
      exec_mode: 'cluster',
      env: {
        NODE_ENV: 'production',
        PORT: 3000,
      },
      error_file: '/home/nextjs/app/logs/pm2-error.log',
      out_file: '/home/nextjs/app/logs/pm2-out.log',
      log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
      merge_logs: true,
      max_memory_restart: '500M',
      restart_delay: 4000,
      min_uptime: '10s',
      max_restarts: 10,
      watch: false,
    }
  ]
}
EOF

Create logs directory:

sudo -u nextjs mkdir -p /home/nextjs/app/logs

Start application with PM2:

cd /home/nextjs/app

# Start with ecosystem file
sudo -u nextjs pm2 start ecosystem.config.js

# List running processes
pm2 list

# Monitor
pm2 monit

Create systemd service for PM2:

# Generate systemd service
sudo pm2 startup systemd -u nextjs --hp /home/nextjs

# Save PM2 process list
sudo -u nextjs pm2 save

# Create service
sudo systemctl enable pm2-nextjs
sudo systemctl start pm2-nextjs
sudo systemctl status pm2-nextjs

Monitor PM2:

# Check status
pm2 status

# View logs
pm2 logs nextjs-app

# Check memory usage
pm2 show nextjs-app

Nginx Configuration

Configure Nginx as reverse proxy for Next.js.

Create Nginx configuration:

sudo cat > /etc/nginx/sites-available/nextjs.conf << 'EOF'
upstream nextjs {
    server 127.0.0.1:3000;
    keepalive 64;
}

server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    access_log /var/log/nginx/nextjs_access.log;
    error_log /var/log/nginx/nextjs_error.log;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/json;
    gzip_min_length 1024;

    # Cache static files
    location ~* ^/_next/static/ {
        expires 365d;
        add_header Cache-Control "public, immutable";
        add_header ETag "";
    }

    # Cache public files
    location ~* \.(ico|png|jpg|jpeg|svg|gif|webp|woff|woff2|ttf|eot)$ {
        expires 30d;
        add_header Cache-Control "public, max-age=2592000";
    }

    # API route caching (adjust as needed)
    location /api/ {
        proxy_pass http://nextjs;
        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_set_header Connection "";
        proxy_http_version 1.1;
        add_header Cache-Control "no-cache, no-store, must-revalidate" always;
    }

    # Proxy to Next.js
    location / {
        proxy_pass http://nextjs;
        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_set_header Connection "";
        proxy_http_version 1.1;
        proxy_buffering off;
        proxy_request_buffering off;
    }
}
EOF

sudo ln -s /etc/nginx/sites-available/nextjs.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Environment Configuration

Properly manage environment variables.

Create .env.production:

# Next.js public variables (accessible in browser)
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_SITE_NAME=My Site

# Server-only variables
DATABASE_URL=postgresql://user:password@localhost/nextjs_db
REDIS_URL=redis://127.0.0.1:6379/0
API_SECRET_KEY=your-secret-key

# Feature flags
NEXT_PUBLIC_FEATURE_BETA=false
NEXT_PUBLIC_ANALYTICS_ID=UA-XXXXXXXXX-X

Load environment in application:

// pages/api/config.js
export default function handler(req, res) {
  res.json({
    apiUrl: process.env.NEXT_PUBLIC_API_URL,
    siteName: process.env.NEXT_PUBLIC_SITE_NAME
  })
}

Verify environment loading:

# Check environment variables loaded
cd /home/nextjs/app
NODE_ENV=production npm run build

# Check build includes .env values
cat .next/server/pages/api/config.js

Caching Strategy

Implement effective caching for performance.

Configure HTTP caching headers:

// next.config.js
async headers() {
  return [
    {
      source: '/',
      headers: [
        {
          key: 'Cache-Control',
          value: 'public, s-maxage=3600, stale-while-revalidate=86400'
        }
      ]
    },
    {
      source: '/:path*',
      headers: [
        {
          key: 'Cache-Control',
          value: 'public, s-maxage=3600'
        }
      ]
    }
  ]
}

Implement ISR (Incremental Static Regeneration):

// pages/posts/[id].js
export default function Post({ post }) {
  return <div>{post.title}</div>
}

export async function getStaticProps({ params }) {
  const post = await fetchPost(params.id)
  
  return {
    props: { post },
    revalidate: 3600  // Revalidate every hour
  }
}

export async function getStaticPaths() {
  const posts = await fetchAllPosts()
  
  return {
    paths: posts.map(post => ({
      params: { id: post.id }
    })),
    fallback: 'blocking'  // Generate new pages on-demand
  }
}

Image Optimization

Optimize images for fast delivery.

Configure Next.js Image component:

// next.config.js
images: {
  formats: ['image/avif', 'image/webp'],
  sizes: [320, 640, 960, 1280, 1920],
}

Use optimized Image component:

import Image from 'next/image'

export default function Hero() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero image"
      width={1920}
      height={1080}
      priority  // Load immediately
      placeholder="blur"  // Blur effect while loading
    />
  )
}

Implement external image optimization:

# Install sharp for image optimization
sudo -u nextjs npm install sharp

# Configure in next.config.js for external images
images: {
  domains: ['cdn.example.com', 'images.example.com']
}

Performance Monitoring

Monitor Next.js application performance.

Check application logs:

# PM2 logs
pm2 logs nextjs-app

# Nginx logs
tail -f /var/log/nginx/nextjs_access.log
tail -f /var/log/nginx/nextjs_error.log

Monitor server resources:

# Check memory usage
ps aux | grep node

# Monitor with top
top -u nextjs

# Check CPU load
uptime

Implement Web Vitals monitoring:

// pages/_app.js
import { useReportWebVitals } from 'next/web-vitals'

export default function App({ Component, pageProps }) {
  useReportWebVitals((metric) => {
    console.log(metric)
  })

  return <Component {...pageProps} />
}

Set up error tracking:

# Install Sentry
sudo -u nextjs npm install @sentry/nextjs

# Configure in next.config.js
withSentryConfig(nextConfig, {
  silent: true,
  org: 'your-org',
  project: 'your-project',
})

Conclusion

Deploying Next.js applications requires Node.js runtime, PM2 process management, Nginx reverse proxy, and proper optimization. This guide covers production-ready deployment with build optimization, PM2 clustering for multiple worker processes, Nginx caching and reverse proxy, environment management, and performance monitoring. Key focus areas are optimized production builds reducing bundle size, PM2 process management ensuring reliability and auto-restart, Nginx caching for static assets and pages, proper environment configuration, and image optimization for fast delivery. Regular monitoring of logs and Web Vitals metrics ensures continued performance. Following these practices creates a fast, reliable Next.js deployment ready for production scale.