Skip to main content
Back to Research

Static Export Optimization in Next.js 16

Getting static export right in Next.js 16 requires understanding Turbopack's chunk model and the levers available for bundle optimization.

Abstract

Next.js static export with Turbopack introduces new chunk splitting behavior and build pipeline characteristics that differ materially from Webpack. This article documents production-proven optimization strategies for reducing bundle size, improving chunk cacheability, and maintaining sub-150kb hero payloads.

May 20, 2026
2 min read

Static export with output: 'export' in Next.js 16 is the correct architecture for content-heavy sites and portfolios. No server to operate, edge-deployable, deterministic build artifacts. The tradeoff: every byte in your bundle is non-negotiable — there is no server-side rendering to bail you out of slow load times.

Turbopack's Chunk Model

Turbopack replaces Webpack's chunk splitting algorithm with a graph-based approach that reasons about module relationships differently. Key behavioral differences:

  • Smaller base chunks: Turbopack produces more granular chunk boundaries by default, which improves cache hit rates for partial updates
  • Aggressive tree-shaking: Turbopack eliminates unused exports more reliably than Webpack, particularly for ESM-only packages
  • Different chunk naming: Turbopack uses content hashes without the predictable naming patterns some cache strategies depend on

Enable Turbopack in next.config.ts:

const nextConfig = {
  output: 'export',
  experimental: {
    turbo: {
      // Turbopack-specific rules
      rules: {
        '*.svg': { loaders: ['@svgr/webpack'], as: '*.js' }
      }
    }
  }
};

The Bundle Analyzer Workflow

Always start with data:

ANALYZE=true npm run build

With @next/bundle-analyzer configured:

// next.config.ts
import withBundleAnalyzer from '@next/bundle-analyzer';

const withAnalyzer = withBundleAnalyzer({
  enabled: process.env.ANALYZE === 'true',
});

export default withAnalyzer(nextConfig);

The treemap will immediately reveal the largest contributors. In most Next.js apps with 3D and animation libraries, the breakdown looks roughly like:

  • Three.js / React Three Fiber: 300-400kb gzipped
  • Framer Motion: 50-80kb gzipped
  • GSAP: 30-60kb gzipped
  • React + React DOM: ~40kb gzipped

Lazy Loading Heavy Dependencies

The most impactful optimization for static export sites:

import { lazy, Suspense } from 'react';

// Never in the initial bundle
const ThreeDScene = lazy(() => import('./ThreeDScene'));
const AnimatedSection = lazy(() => import('./AnimatedSection'));

export function HomePage() {
  return (
    <>
      <HeroSection /> {/* Synchronous — in initial bundle */}
      <Suspense fallback={<div className="h-96 animate-pulse bg-muted" />}>
        <ThreeDScene />
      </Suspense>
    </>
  );
}

This moves Three.js out of the critical path entirely. The hero renders immediately; the 3D scene loads after.

Splitting GSAP Plugins

GSAP's plugin model is treeshakeable but only if you import selectively:

// ❌ Imports entire GSAP
import gsap from 'gsap';

// ✅ Import only what you use
import { gsap } from 'gsap/dist/gsap';
import { ScrollTrigger } from 'gsap/dist/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

Combined with dynamic import in the component that needs it, GSAP plugins stay out of the initial bundle.

Route-Level Code Splitting

App Router handles this automatically — each route segment is its own chunk. But admin panels and dashboard routes deserve explicit attention:

// app/admin/page.tsx — heavy admin bundle, isolated from public routes
// This chunk is never loaded for public visitors

Verify isolation with the analyzer: the admin chunk should contain all admin-specific code and none of it should appear in the landing page chunks.

Preload Critical Resources

For above-the-fold fonts and critical CSS:

// app/layout.tsx
<head>
  <link rel="preconnect" href="https://fonts.googleapis.com" />
  <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
  <link
    rel="preload"
    href="/fonts/inter-var.woff2"
    as="font"
    type="font/woff2"
    crossOrigin=""
  />
</head>

With next/font/google, Next.js handles font optimization automatically — fonts are inlined or preloaded based on the route. Self-hosted fonts via next/font/local give you the most control.

Practical Targets

Resource Target
Hero + nav initial JS < 150kb gzipped
LCP element < 200ms with preload hint
Total page JS < 500kb gzipped (warn above)
Any single chunk < 100kb gzipped

Run npm run build and check the output table — Next.js flags first-load JS above ~100kb in yellow and above ~300kb in red per route. Both colors represent work to do.

Continue Reading
JCJOOTACEE / OPS

Operational laboratory for AI systems, automation infrastructures, and modular digital ecosystems.

Systems

  • AURA Orchestration
  • MCP Ecosystem
  • Graph Memory
  • AI Agents
  • Docker Infrastructure
  • Industrial Intelligence

System Status

PlatformOperational
APIHealthy
3D EngineActive
MCP Nodes8 Online

Try the Konami code...

Stay in the loop

Occasional updates on AI systems, autonomous infrastructure, and new releases.

© 2026 JootaCee. All systems operational.

RSSChangelogNext.js 16 + React 19 + R3F + GSAP