Agamya Samuel
Available for work

Deploying Next.js to Static Export with Docker

September 10, 20242 min read

The Challenge

When building my portfolio, I wanted the best of both worlds: the developer experience of Next.js and the simplicity of static hosting. The output: 'export' configuration in Next.js makes this possible, but there are nuances to understand.

Why Static Export?

Static sites offer significant advantages:

  • Performance — no server-side rendering at request time
  • Security — no server to compromise
  • Cost — host on any static hosting (S3, Cloudflare Pages, Nginx)
  • Reliability — nothing to go wrong at runtime

The Configuration

The key configuration in next.config.mjs:

const nextConfig = {
    output: 'export',
    images: {
        unoptimized: true,
    },
};

Important: When using output: 'export', Next.js Image Optimization is not available. You must set images.unoptimized: true or use a third-party image loader.

Dynamic Routes with Static Export

Dynamic routes like /blog/[slug] require generateStaticParams to tell Next.js which pages to pre-render:

export function generateStaticParams() {
    return posts.map((post) => ({
        slug: post.slug,
    }));
}
 
export default function BlogPost({ params }: { params: { slug: string } }) {
    const post = posts.find((p) => p.slug === params.slug);
    // render post...
}

Docker Deployment

For containerized deployment, a multi-stage Dockerfile keeps the image small:

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
 
FROM nginx:alpine
COPY --from=builder /app/out /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

The resulting image is roughly 25MB — just Nginx serving static files.

Nginx Configuration

A minimal Nginx config for SPA-style routing:

server {
    listen 80;
    root /usr/share/nginx/html;
    index index.html;
 
    location / {
        try_files $uri $uri.html $uri/ /index.html;
    }
 
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
}

Results

The portfolio loads in under 500ms on average, scores 100/100 on Lighthouse performance, and costs essentially nothing to host. The static export approach combined with Docker gives me flexibility to deploy anywhere — from a $5 VPS to a Kubernetes cluster.

Key Takeaways

  1. Static export is perfect for content-heavy sites that don't need real-time data
  2. generateStaticParams is essential for any dynamic routes
  3. Multi-stage Docker builds keep production images minimal
  4. Nginx caching headers dramatically improve repeat visits
Agamya Samuel

Agamya Samuel

Software Developer & Cloud Architect