BlogSoftware Development
Software Development

Next.js Static Export for Production: Lessons from the Field

Performance optimizations, cache busting strategies, and deployment patterns we've learned building enterprise marketing sites with Next.js.

Sindika Engineering Feb 28, 2026 6 min read

Your marketing team wants the website to load instantly. Your CFO wants the hosting bill to be “as close to zero as possible.” Your DevOps engineer wants to stop babysitting a Node.js server at 3 AM. And your SEO consultant wants a perfect Lighthouse score.

Sounds impossible? It's not. Next.js static export delivers all four — if you know the patterns that make it work in production.

“We rebuilt our corporate site as a Next.js static export served by Nginx. TTFB dropped from 400ms to 12ms. Monthly hosting went from $150 to $8. And deployments became a simple Docker image swap with zero downtime.”

— Sindika Engineering Team

Chapter 1: Why Static Export?

Next.js is known for its server-side rendering capabilities. But here's what most tutorials don't tell you: not every site needs a server. Marketing sites, documentation portals, company landing pages, blogs — these are fundamentally static content that changes when you deploy, not when a user requests a page.

With output: 'export' in your next.config.js, Next.js generates plain HTML, CSS, and JavaScript files. No Node.js runtime. No server processes. Just files that any web server — Nginx, Apache, Cloudflare Pages, even S3 — can serve.

Static Export PipelineNext.jsReact/TSX⚛️Buildnext build🔨ExportHTML/CSS/JS📦NginxCDN / Edge🚀Zero RuntimeInstant TTFBGlobal CDNNo Node.js server required — pure static files

The build pipeline: React components compile to static HTML/CSS/JS, served directly by Nginx with zero runtime overhead.

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
    output: 'export',        // Generate static files
    trailingSlash: true,     // /about/ instead of /about
    images: {
        unoptimized: true,   // No Image Optimization API
    },
};

module.exports = nextConfig;

Chapter 2: The Cache Busting Problem Nobody Warns You About

Here's the nightmare scenario that hit us in production: you deploy a new version. Next.js generates new chunk files with new hashes (app-xyz789.js). But users who still have the old HTML cached in their browser are trying to load app-abc123.js — which no longer exists on your server.

The result? ChunkLoadError. A white screen. An angry user. And a support ticket that says “YOUR WEBSITE IS BROKEN.”

Cache Busting StrategyOld Deploy (Cached)app-abc123.jsstyles-def456.css❌ ChunkLoadErrorNew Deploy (Live)app-xyz789.jsstyles-uvw012.css✓ Fresh chunksSolution: Global Error Handlerwindow.addEventListener('error', handler)Detect ChunkLoadError → Clear cache → Reload✓ Zero downtime deploys

🤔 The Stale Cache Trap

  • Browser caches HTML aggressively — even with proper cache headers, some browsers and CDNs hold onto the old HTML longer than expected.
  • Service workers make it worse — if you have a PWA service worker, it may serve stale HTML from its cache indefinitely.
  • Rate limiting compounds the issue — if your server returns 429 when the browser retries fetching chunks, the error handler never gets a chance to recover.

Our solution: a global error handler that catches ChunkLoadError before React even mounts. It detects the stale-cache situation, clears the browser cache, and triggers a full page reload to fetch fresh assets. Combined with Nginx rules that gracefully handle missing chunks, this creates a self-healing deployment.

<!-- Injected before React bundle in _document or layout -->
<script>
  window.addEventListener('error', function(e) {
    if (e.message && (
      e.message.includes('ChunkLoadError') ||
      e.message.includes('Loading chunk')
    )) {
      // Clear all caches and reload
      if ('caches' in window) {
        caches.keys().then(names =>
          Promise.all(names.map(n => caches.delete(n)))
        ).then(() => window.location.reload());
      } else {
        window.location.reload();
      }
    }
  });
</script>

Chapter 3: The Nginx Configuration That Makes It All Work

Nginx is the unsung hero of static deployments. A well-configured Nginx turns your static export from “files on a disk” into a production-grade web application with compression, caching, security headers, and SPA routing.

Production Deployment ArchitectureGitLab CIBuild + TestDockerMulti-stageNginxStatic serveUsers🌍Nginx Configuration Highlightsgzip on — Compress all static assets (70%+ smaller)Cache-Control: max-age=31536000 — Immutable hashed assetstry_files $uri /index.html — SPA fallback routingerror_page 404 /404.html — Custom error pages

GitLab CI builds the Docker image → Nginx serves the static files → Users get sub-50ms responses worldwide.

# Key Nginx patterns for Next.js static export

# 1. Hashed assets — cache forever
location /_next/static/ {
    expires max;
    add_header Cache-Control "public, immutable";
}

# 2. HTML pages — revalidate on every request
location / {
    try_files $uri $uri/index.html $uri.html /index.html;
    add_header Cache-Control "no-cache, must-revalidate";
}

# 3. Missing chunks — redirect to home (not 404)
location ~* \.(js|css)$ {
    try_files $uri /index.html;
}

# 4. Compression — 70%+ size reduction
gzip on;
gzip_types text/html application/javascript text/css;

✅ Production Nginx Checklist

  • Immutable caching for hashed assets/_next/static/ files have content hashes. Cache them forever with Cache-Control: immutable.
  • No-cache for HTML — HTML files should always revalidate so users get the latest chunk references after deploys.
  • SPA fallback routing — use try_files to handle client-side routing. Every path should fall back to the appropriate index.html.
  • Security headers — add X-Frame-Options, X-Content-Type-Options, and CSP headers. Static doesn't mean insecure.
  • Rate limiting — protect against DDoS with limit_req_zone. Even static sites need protection.

Chapter 4: When Static Export Wins (and When It Doesn't)

Let's be honest about the tradeoffs. Static export isn't a silver bullet — it's a precision tool for a specific class of websites.

MetricSSR (Server)Static Export
TTFB200-800ms10-50ms
Lighthouse Score75-9095-100
Hosting Cost$50-200/mo$0-20/mo
Server RequiredNode.jsAny CDN/Nginx
Dynamic Content✓ NativeClient-side only
Auth/Sessions✓ Server-sideExternal service
Build TimeFast (on-demand)Slow (every page)
ScalabilityNeeds scalingInfinite (CDN)

The pattern is clear: if your content changes at deploy time rather than request time, static export gives you better performance, lower costs, and simpler operations. The moment you need per-request personalization, authentication-gated content, or real-time data — reach for SSR or client-side fetching.

And here's the hybrid approach we use: static export for the shell, with client-side API calls for dynamic sections. The page structure, navigation, and static content load instantly from the CDN. Interactive elements like contact forms, search, and live data hydrate on the client after the initial paint.

Chapter 5: The Docker Multi-Stage Pattern

Our production Dockerfile is a two-stage build that keeps the final image tiny — just Nginx and your static files. No Node.js runtime, no node_modules, no build tools in production.

# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build    # Generates /app/out

# Stage 2: Serve
FROM nginx:alpine
COPY --from=builder /app/out /usr/share/nginx/html
COPY nginx/default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80

# Final image: ~25MB (vs ~1GB with Node.js)

The result? A 25MB Docker image that starts in milliseconds, uses almost no memory, and serves thousands of concurrent requests without breaking a sweat. Compare that to a Node.js SSR container that needs 512MB+ of RAM and takes seconds to cold-start.

“The best server is no server. When your website is just files on a CDN, there's nothing to crash, nothing to scale, and nothing to patch at 2 AM. That's not laziness — it's operational maturity.”

— Sindika Engineering Team

The Bottom Line

Next.js static export isn't just for hobby projects. With the right cache busting strategy, Nginx configuration, and Docker multi-stage build, it becomes a production-grade deployment pattern that outperforms SSR for content-driven websites.

12ms TTFB. $8/month hosting. Zero-downtime deploys. Perfect Lighthouse scores. If your site doesn't need per-request server rendering, stop paying for a server.