Skip to main content

Introduction

This guide covers deploying the Next.js frontend application to production, including build optimization, environment configuration, and various hosting options.

Prerequisites

Before deploying, ensure you have:
  • Node.js 18+ installed
  • Production backend API URL
  • Environment variables configured
  • SSL certificate for your domain

Rendering Strategies

Next.js 16 supports multiple rendering strategies. Choose based on your needs:
Best for: Dynamic content, personalized experiences, SEO-critical pagesPros:
  • Real-time data fetching
  • Better SEO
  • Authenticated routes
  • Dynamic API calls
Cons:
  • Requires Node.js server
  • Higher hosting costs
  • More complex deployment
No configuration needed - this is the default Next.js behavior.
For Laravel Breeze API integration, SSR (default) is recommended as it supports authenticated routes and dynamic data fetching.

Production Build

1. Install Dependencies

# Using npm
npm install

# Using pnpm (recommended)
pnpm install

# Using yarn
yarn install

2. Configure Environment Variables

Create a .env.production file:
.env.production
NEXT_PUBLIC_BACKEND_URL=https://api.yourdomain.com
Only variables prefixed with NEXT_PUBLIC_ are accessible in the browser. Never expose sensitive API keys or secrets with this prefix.

3. Build for Production

npm run build
This command:
  • Compiles TypeScript
  • Optimizes JavaScript bundles
  • Generates static pages
  • Creates production-ready assets

4. Test Production Build Locally

npm start
Visit http://localhost:3000 to test the production build.

5. Analyze Bundle Size

Optimize your bundle size:
# Install bundle analyzer
npm install @next/bundle-analyzer

# Add to next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
})

module.exports = withBundleAnalyzer(nextConfig)

# Analyze
ANALYZE=true npm run build

Hosting Options

Vercel provides zero-configuration deployment optimized for Next.js.
1

Install Vercel CLI

npm install -g vercel
2

Login to Vercel

vercel login
3

Deploy

# First deployment (creates project)
vercel

# Production deployment
vercel --prod
4

Configure Environment Variables

In Vercel dashboard:
  1. Go to Project Settings → Environment Variables
  2. Add NEXT_PUBLIC_BACKEND_URL
  3. Set value to your production API URL
  4. Redeploy the application
Vercel Configuration (vercel.json)
vercel.json
{
  "buildCommand": "npm run build",
  "outputDirectory": ".next",
  "framework": "nextjs",
  "rewrites": [
    {
      "source": "/api/:path*",
      "destination": "https://api.yourdomain.com/:path*"
    }
  ]
}

Option 2: Netlify

Netlify offers simple deployment with Git integration.
1

Install Netlify CLI

npm install -g netlify-cli
2

Login to Netlify

netlify login
3

Initialize Site

netlify init
4

Deploy

# Deploy to production
netlify deploy --prod
Netlify Configuration (netlify.toml)
netlify.toml
[build]
  command = "npm run build"
  publish = ".next"

[[plugins]]
  package = "@netlify/plugin-nextjs"

[[redirects]]
  from = "/api/*"
  to = "https://api.yourdomain.com/:splat"
  status = 200
  force = true

[build.environment]
  NEXT_PUBLIC_BACKEND_URL = "https://api.yourdomain.com"

Option 3: Custom Node.js Server

Deploy to your own VPS or cloud instance.
1

Install PM2

npm install -g pm2
2

Build Application

npm run build
3

Start with PM2

pm2 start npm --name "nextjs-app" -- start

# Or use ecosystem file
pm2 start ecosystem.config.js
4

Configure PM2 Startup

pm2 startup
pm2 save
PM2 Ecosystem File (ecosystem.config.js)
ecosystem.config.js
module.exports = {
  apps: [{
    name: 'nextjs-app',
    script: 'node_modules/next/dist/bin/next',
    args: 'start',
    cwd: '/var/www/yourdomain.com',
    instances: 'max',
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'production',
      PORT: 3000,
      NEXT_PUBLIC_BACKEND_URL: 'https://api.yourdomain.com'
    },
    error_file: 'logs/err.log',
    out_file: 'logs/out.log',
    log_date_format: 'YYYY-MM-DD HH:mm Z',
    max_memory_restart: '1G',
    autorestart: true,
  }]
}
Nginx Configuration
server {
    listen 80;
    listen [::]:80;
    server_name app.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name app.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/app.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.yourdomain.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    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;
    }
}

Option 4: Docker Deployment

Containerize your Next.js application. Dockerfile
Dockerfile
# Build stage
FROM node:18-alpine AS builder

WORKDIR /app

# Copy package files
COPY package.json pnpm-lock.yaml ./

# Install dependencies
RUN npm install -g pnpm && pnpm install --frozen-lockfile

# Copy source code
COPY . .

# Build application
ENV NEXT_TELEMETRY_DISABLED 1
RUN pnpm build

# Production stage
FROM node:18-alpine AS runner

WORKDIR /app

ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1

# Create non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# Copy built application
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000

CMD ["node", "server.js"]
Update next.config.js for standalone output:
next.config.js
const nextConfig = {
  output: 'standalone',
}

module.exports = nextConfig
Build and run:
# Build image
docker build -t nextjs-app .

# Run container
docker run -p 3000:3000 \
  -e NEXT_PUBLIC_BACKEND_URL=https://api.yourdomain.com \
  nextjs-app

Environment Variables

Public Variables

These are embedded in the client-side bundle:
NEXT_PUBLIC_BACKEND_URL=https://api.yourdomain.com
NEXT_PUBLIC_APP_NAME="Your App Name"

Server-Only Variables

These are only available on the server:
API_SECRET_KEY=your-secret-key
DATABASE_URL=postgresql://...
Never prefix sensitive secrets with NEXT_PUBLIC_. They will be exposed to the browser.

Performance Optimization

1. Image Optimization

import Image from 'next/image'

export default function Avatar() {
  return (
    <Image
      src="/avatar.jpg"
      alt="User Avatar"
      width={100}
      height={100}
      priority // For above-the-fold images
    />
  )
}

2. Code Splitting

import dynamic from 'next/dynamic'

const DynamicComponent = dynamic(() => import('../components/Heavy'), {
  loading: () => <p>Loading...</p>,
  ssr: false, // Disable server-side rendering if not needed
})

3. Font Optimization

app/layout.tsx
import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'] })

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={inter.className}>
      <body>{children}</body>
    </html>
  )
}

4. Caching Strategy

// Revalidate every hour
export const revalidate = 3600

// Or use on-demand revalidation
import { revalidatePath } from 'next/cache'

revalidatePath('/dashboard')

Security Considerations

Add CSP headers in next.config.js:
const nextConfig = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Content-Security-Policy',
            value: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
          },
        ],
      },
    ]
  },
}
next.config.js
const securityHeaders = [
  {
    key: 'X-DNS-Prefetch-Control',
    value: 'on'
  },
  {
    key: 'Strict-Transport-Security',
    value: 'max-age=63072000; includeSubDomains; preload'
  },
  {
    key: 'X-Frame-Options',
    value: 'SAMEORIGIN'
  },
  {
    key: 'X-Content-Type-Options',
    value: 'nosniff'
  },
  {
    key: 'Referrer-Policy',
    value: 'origin-when-cross-origin'
  },
]

module.exports = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: securityHeaders,
      },
    ]
  },
}
lib/env.ts
function getEnvVar(key: string): string {
  const value = process.env[key]
  if (!value) {
    throw new Error(`Missing environment variable: ${key}`)
  }
  return value
}

export const env = {
  backendUrl: getEnvVar('NEXT_PUBLIC_BACKEND_URL'),
}

Continuous Deployment

GitHub Actions

Create .github/workflows/deploy.yml:
.github/workflows/deploy.yml
name: Deploy Next.js

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run linter
        run: npm run lint
      
      - name: Build
        run: npm run build
        env:
          NEXT_PUBLIC_BACKEND_URL: ${{ secrets.NEXT_PUBLIC_BACKEND_URL }}
      
      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v20
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.ORG_ID }}
          vercel-project-id: ${{ secrets.PROJECT_ID }}
          vercel-args: '--prod'

Troubleshooting

  • Verify NEXT_PUBLIC_BACKEND_URL is set correctly
  • Check CORS configuration on backend
  • Ensure SSL certificates are valid
  • Test API endpoint directly: curl https://api.yourdomain.com/api/health
  • Check Node.js version: node --version
  • Clear build cache: rm -rf .next
  • Reinstall dependencies: rm -rf node_modules && npm install
  • Review build logs for TypeScript errors
  • Verify cookies are being set (check browser DevTools)
  • Ensure both frontend and backend use HTTPS
  • Check SESSION_DOMAIN and SANCTUM_STATEFUL_DOMAINS in backend
  • Verify axios configuration includes withCredentials: true
  • Analyze bundle size: ANALYZE=true npm run build
  • Enable code splitting for large components
  • Use Next.js Image component for images
  • Implement caching strategies
  • Consider CDN for static assets

Monitoring

Vercel Analytics

If using Vercel, enable built-in analytics:
app/layout.tsx
import { Analytics } from '@vercel/analytics/react'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Analytics />
      </body>
    </html>
  )
}

Custom Error Tracking

npm install @sentry/nextjs
sentry.client.config.js
import * as Sentry from '@sentry/nextjs'

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 1.0,
})

Next Steps

Backend Deployment

Deploy your Laravel backend API

Environment Configuration

Configure production environment variables

Authentication

Learn about frontend authentication

API Integration

Integrate with your Laravel API

Build docs developers (and LLMs) love