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. Best for: Marketing pages, blogs, documentationPros:
Fastest performance
Cheapest hosting
Best security
CDN-friendly
Cons:
No server-side data
Rebuild required for updates
Limited authentication support
Configure in next.config.js: /** @type {import('next').NextConfig} */
const nextConfig = {
output: 'export' ,
images: {
unoptimized: true ,
},
}
module . exports = nextConfig
Best for: Most applications (recommended)Pros:
Flexible per-page rendering
Optimal performance
Best of both worlds
Cons:
Requires understanding of rendering modes
Mix static and dynamic pages using Next.js data fetching methods.
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
Create a .env.production file:
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
This command:
Compiles TypeScript
Optimizes JavaScript bundles
Generates static pages
Creates production-ready assets
4. Test Production Build Locally
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
Option 1: Vercel (Recommended)
Vercel provides zero-configuration deployment optimized for Next.js.
Deploy
# First deployment (creates project)
vercel
# Production deployment
vercel --prod
Configure Environment Variables
In Vercel dashboard:
Go to Project Settings → Environment Variables
Add NEXT_PUBLIC_BACKEND_URL
Set value to your production API URL
Redeploy the application
Vercel Configuration (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.
Install Netlify CLI
npm install -g netlify-cli
Deploy
# Deploy to production
netlify deploy --prod
Netlify Configuration (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.
Start with PM2
pm2 start npm --name "nextjs-app" -- start
# Or use ecosystem file
pm2 start ecosystem.config.js
PM2 Ecosystem File (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
# 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:
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.
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
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';"
},
],
},
]
},
}
Environment Variable Validation
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
Authentication Not Working
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
Monitoring
Vercel Analytics
If using Vercel, enable built-in analytics:
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
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