Skip to content

SSR Deployment — Deploying SSR Applications to Production

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about SSR Deployment. We cover key concepts, practical examples, and best practices to help you master this topic.

SSR deployment covers deploying Node.js SSR applications to production servers, serverless platforms like Vercel and Netlify, Docker containers, and Kubernetes with proper scaling, monitoring, and performance optimization.

What You'll Learn

By the end of this tutorial, you will understand how to deploy SSR applications to various environments, configure production-ready servers, set up process managers, implement horizontal scaling, configure CDN caching, monitor SSR performance, and handle deployment rollbacks.

Why It Matters

Deploying SSR applications is more complex than deploying static sites or SPAs. SSR requires a running server that handles requests, manages memory, and scales under load. A misconfigured deployment leads to slow responses, server crashes, and downtime. Proper deployment ensures your SSR application is fast, reliable, and scalable.

Real-World Use

A Next.js SSR application deployed to a single Node.js server handled 100 requests per second. After implementing horizontal scaling with a load balancer and containerization, the same application handled 10,000 requests per second across 20 containers. CDN caching reduced server load by 80 percent for public pages.

SSR Deployment Architecture
    ┌──────────────────────────────────────────────────────────┐
    │              SSR Production Architecture                 │
    ├──────────────────────────────────────────────────────────┤
    │                                                          │
    │  Users → CDN (CloudFlare, CloudFront)                   │
    │              │                                           │
    │              ├── Cached HTML (CDN edge)                 │
    │              │   (for public pages with Cache-Control)  │
    │              │                                           │
    │              └── Load Balancer (ALB, Nginx)             │
    │                     │                                   │
    │                     ├── SSR Instance 1 (Docker/EC2)     │
    │                     ├── SSR Instance 2 (Docker/EC2)     │
    │                     ├── SSR Instance 3 (Docker/EC2)     │
    │                     └── SSR Instance N (auto-scaled)   │
    │                                                          │
    │  Each SSR Instance:                                      │
    │    • Node.js server (PM2 process manager)                │
    │    • Redis cache connection                              │
    │    • Database connection pool                            │
    │    • Health check endpoint                               │
    │    • Error logging (Sentry)                              │
    │                                                          │
    └──────────────────────────────────────────────────────────┘

Think of SSR deployment like running a chain of restaurants. You cannot just build one kitchen and hope it handles all customers. You need a supply chain (CDN), reservation system (load balancer), multiple kitchen locations (server instances), quality monitoring (logging), and the ability to open new locations when demand increases (auto-scaling).

Deploying to a Node.js Server

# 1. Build the SSR application
npm run build

# 2. Install PM2 process manager
npm install -g pm2

# 3. Create ecosystem.config.js
module.exports = {
    apps: [{
        name: 'ssr-app',
        script: 'server.js',
        instances: 'max',          // Use all CPU cores
        exec_mode: 'cluster',      // Cluster mode for load balancing
        env: {
            NODE_ENV: 'production',
            PORT: 3000,
            REDIS_URL: 'redis://...',
            DATABASE_URL: 'postgresql://...'
        },
        max_memory_restart: '500M', // Restart if memory exceeds 500MB
        error_file: 'logs/err.log',
        out_file: 'logs/out.log',
        merge_logs: true,
        log_date_format: 'YYYY-MM-DD HH:mm:ss',
        watch: false,
        max_restarts: 10,
        restart_delay: 4000
    }]
};

# 4. Start with PM2
pm2 start ecosystem.config.js

# 5. Save PM2 process list (auto-start on reboot)
pm2 save
pm2 startup

# 6. Configure Nginx reverse proxy
# /etc/nginx/sites-available/ssr-app
server {
    listen 80;
    server_name example.com;

    # Redirect to HTTPS
    return 301 https://$server_name$request_uri;
}

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

    # SSL configuration
    ssl_certificate /etc/ssl/certs/example.com.pem;
    ssl_certificate_key /etc/ssl/private/example.com.key;

    # Proxy to SSR server
    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_cache_bypass $http_upgrade;
    }

    # Cache static assets
    location /_next/static {
        proxy_pass http://localhost:3000;
        expires 365d;
        add_header Cache-Control "public, immutable";
    }
}

Deploying to Vercel

// vercel.json — Vercel configuration for Next.js SSR
{
    "buildCommand": "npm run build",
    "outputDirectory": ".next",
    "installCommand": "npm install",
    "framework": "nextjs",
    "regions": ["iad1", "hkg1", "lhr1"],
    "functions": {
        "api/**/*.js": {
            "memory": 512,
            "maxDuration": 30
        }
    },
    "headers": [
        {
            "source": "/(.*)",
            "headers": [
                {
                    "key": "X-Content-Type-Options",
                    "value": "nosniff"
                },
                {
                    "key": "X-Frame-Options",
                    "value": "DENY"
                }
            ]
        },
        {
            "source": "/_next/static/(.*)",
            "headers": [
                {
                    "key": "Cache-Control",
                    "value": "public, max-age=31536000, immutable"
                }
            ]
        }
    ]
}

// Environment variables in Vercel:
//   NODE_ENV=production
//   REDIS_URL=redis://...
//   DATABASE_URL=postgresql://...
//   SENTRY_DSN=https://...

Monitoring and Health Checks

// Health check endpoint for load balancer
app.get('/api/health', async (req, res) => {
    const health = {
        status: 'healthy',
        timestamp: new Date().toISOString(),
        uptime: process.uptime(),
        memory: process.memoryUsage(),
        version: process.env.npm_package_version
    };

    try {
        // Check database connection
        await db.raw('SELECT 1');
        health.database = 'connected';
    } catch (error) {
        health.database = 'disconnected';
        health.status = 'degraded';
    }

    try {
        // Check Redis connection
        await redis.ping();
        health.redis = 'connected';
    } catch (error) {
        health.redis = 'disconnected';
        health.status = 'degraded';
    }

    const statusCode = health.status === 'healthy' ? 200 : 503;
    res.status(statusCode).json(health);
});

// Expected health check response:
// {
//   "status": "healthy",
//   "timestamp": "2026-06-28T10:00:00.000Z",
//   "uptime": 123456,
//   "memory": { rss: 150000000, heapTotal: 80000000, heapUsed: 60000000 },
//   "database": "connected",
//   "redis": "connected"
// }

Common Mistakes

  1. Running SSR with a single process. Node.js is single-threaded. Without clustering (PM2 cluster mode), you can only handle one request at a time. Always use all CPU cores.
  2. No health checks. Load balancers need health check endpoints to know if an instance is healthy. Without them, traffic is sent to failing instances.
  3. Not setting memory limits. SSR can leak memory over time. Set max_memory_restart in PM2 to automatically restart instances that exceed memory limits.
  4. Cold starts on serverless. Serverless SSR (Vercel, Netlify) has cold starts of 100-500ms. Consider dedicated servers for latency-sensitive applications.
  5. No CDN caching. Without CDN caching, every request hits your SSR server. Cache public pages aggressively at the CDN level.

Practice Questions

  1. How do you deploy an SSR application with PM2 cluster mode?
  2. What is the purpose of a health check endpoint?
  3. How do you configure CDN caching for SSR pages?
  4. What are the tradeoffs between serverless and dedicated SSR hosting?
  5. How do you monitor SSR application health in production?

Challenge: Deploy an SSR application to production: set up a Node.js server with PM2 cluster mode, configure Nginx reverse proxy with SSL, implement health check endpoint, set up Redis caching with connection pooling, configure CDN (Cloudflare or CloudFront) for caching, set up Sentry error tracking, create a deployment script, and test with a load testing tool (k6 or autocannon).

FAQ

Should I use Docker for SSR deployment?

Yes. Docker provides consistency across environments, simplifies scaling, and integrates with orchestration tools (Kubernetes, ECS). Use multi-stage builds for smaller images.

How many SSR instances do I need?

Start with 2-3 instances behind a load balancer. Monitor CPU and memory usage. Auto-scale when average CPU exceeds 70 percent.

What is the best way to handle SSR deployments?

Use blue-green deployment: deploy a new version alongside the current one, test it, then switch traffic. This allows instant rollbacks if something goes wrong.

How do I handle database migrations in SSR deployment?

Run migrations as a separate step before deploying the new version. Use a migration tool (Knex, Prisma) and ensure backward compatibility.

Does SSR work with edge computing?

Yes. Platforms like Vercel Edge Functions and Cloudflare Workers support SSR at the edge. However, limitations on execution time and memory may apply.

Mini Project

Set up a complete SSR production deployment: Docker multi-stage build for the SSR application, PM2 cluster mode with ecosystem.config.js, Nginx reverse proxy with SSL termination and static asset caching, health check endpoint with database and Redis checks, CDN (Cloudflare) for public page caching, Sentry error monitoring, GitHub Actions CI/CD pipeline with blue-green deployment, and k6 load testing to verify the setup handles 1000 RPS.

What's Next

You understand SSR deployment. Now build a complete SSR mini project that combines everything you have learned.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro