Deploying a Nuxt 3 application with Server-Side Rendering (SSR) on Laravel Forge requires specific configurations to ensure that Nuxt is properly served. This guide provides a step-by-step walkthrough to deploy your Nuxt 3 SSR app on Forge, configure Nginx, and manage Nuxt processes using PM2.


Step 1: Prepare Your Nuxt 3 App for Deployment

Before deploying, ensure your Nuxt app is properly configured for SSR. Update your nuxt.config.js file:

export default defineNuxtConfig({
  ssr: true, // Enable Server-Side Rendering
  nitro: {
    prerender: {
      crawlLinks: false, // Prevent Nitro from crawling and generating routes
      routes: [], // No pre-rendered routes
    },
  },
  // ... other config here
})

Why this matters?

  • When SSR is enabled, Nuxt does not serve static files from /dist.
  • Instead, the SSR application is served from .output/public/server.
  • This means all requests are handled dynamically by Nuxt.
  • We need PM2 to manage the Nuxt server, ensuring it runs continuously.

Step 2: Create a New Static App on Laravel Forge

  1. Log in to your Laravel Forge account.
  2. Create a new site and select Static App as the site type.
  3. Add your domain (e.g., yourdomain.com).
  4. Assign an SSL certificate (Let's Encrypt or manually).
  5. Once SSL is active, edit the Nginx configuration file.

Editing Nginx Configuration on Forge

Rather than editing the Nginx config directly on the server, follow these steps:

  1. Navigate to your site on Forge.
  2. Locate the Edit Files dropdown on the top-right side of the page.
  3. Select Edit Nginx Configuration.
  4. Replace the existing configuration with the following, ensuring you replace:
    • YOUR_DOMAIN_NAME with your actual domain.
    • YOUR_DOMAIN_FOLDER with the correct site directory.
    • Keep the SSL ID values assigned by Forge for your certificate.

Example:

# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/YOUR_DOMAIN_FOLDER/before/*;

map $sent_http_content_type $expires {
    "text/html"                 epoch;
    "text/html; charset=utf-8"  epoch;
    default                     off;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name YOUR_DOMAIN_NAME;

    # FORGE SSL (DO NOT REMOVE!)
    ssl_certificate /etc/nginx/ssl/YOUR_DOMAIN_NAME/YOUR_SSL_ID/server.crt;
    ssl_certificate_key /etc/nginx/ssl/YOUR_DOMAIN_NAME/YOUR_SSL_ID/server.key;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_dhparam /etc/nginx/dhparams.pem;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    charset utf-8;

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

    # FORGE CONFIG (DO NOT REMOVE!)
    include forge-conf/YOUR_DOMAIN_FOLDER/server/*;

    location / {
        expires $expires;

        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_redirect              off;
        proxy_read_timeout          1m;
        proxy_connect_timeout       1m;
        proxy_pass                  http://127.0.0.1:3000; # Set the address of the Node.js server
    }

    access_log off;
    error_log  /var/log/nginx/YOUR_DOMAIN_NAME-error.log error;

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

# FORGE CONFIG (DO NOT REMOVE!)
include forge-conf/YOUR_DOMAIN_FOLDER/after/*;

After saving changes in Forge, restart Nginx by clicking Apply Changes.


Step 3: Set Up Deployment Script on Forge

Navigate to your site on Laravel Forge and go to the Deployment tab. Update the deployment script with the following:

#!/bin/bash
cd /home/forge/YOUR_DOMAIN_NAME

# Pull latest changes
git pull origin $FORGE_SITE_BRANCH

# Clean installation files
rm -rf node_modules
rm package-lock.json

# Install dependencies with platform-specific flags
export ROLLUP_SKIP_NODE_CHECK=true
npm install --platform=linux --arch=x64

# Build for SSR
npm run build

# Restart Nuxt using PM2
pm2 delete nuxt-app || true  # Ensure old process is removed
pm2 start .output/server/index.mjs --name "nuxt-app"

# Save PM2 process so it persists after a reboot
pm2 save

# Restart PHP-FPM (if needed for other Laravel services)
( flock -w 10 9 || exit 1
    echo 'Restarting FPM...'; sudo -S service $FORGE_PHP_FPM reload ) 9>/tmp/fpmlock

Click Save and then Deploy Now to start the process.


Step 4: Finalizing Your Deployment

  • Ensure that your Web Directory in Laravel Forge is set to:
/public
  • Manually restart all services to ensure everything is working:
sudo service nginx restart
pm2 restart nuxt-app
  • Open your domain in the browser (https://yourdomain.com) and check that the app is loading correctly.

  • Monitor logs if there are any issues:

tail -f /var/log/nginx/YOUR_DOMAIN_NAME-error.log
pm2 logs nuxt-app

Conclusion

By following these steps, you have successfully deployed a Nuxt 3 SSR application on Laravel Forge. This setup ensures:

  • Nuxt 3 runs in server-side rendering mode.
  • Nginx properly proxies requests to the Nuxt SSR server.
  • PM2 keeps the Nuxt process running reliably.

This guide should serve as a complete reference for anyone deploying Nuxt 3 SSR on Laravel Forge. 🚀