Build Output Analysis
Understanding your build output is essential for optimizing production deployments. It tells you exactly what's being generated and how.
Running a Production Build
npm run build
This generates optimized output in .next/ and shows a detailed report.
Reading the Build Output
Route (app) Size First Load JS
ā ā / 5.2 kB 89.1 kB
ā ā /about 2.1 kB 86.0 kB
ā ā /blog/[slug] 3.4 kB 87.3 kB
ā ā /blog/hello-world
ā ā /blog/nextjs-guide
ā ā [+8 more paths]
ā Ī» /dashboard 12.8 kB 96.7 kB
ā ā /pricing 1.8 kB 85.7 kB
ā Ī» /api/users 0 B 0 B
+ First Load JS shared by all 83.9 kB
ā chunks/framework-abc123.js 45.2 kB
ā chunks/main-def456.js 27.1 kB
ā other shared chunks 11.6 kB
Route Symbols
| Symbol | Meaning | Cached |
|---|---|---|
| ā | Static | At build time |
| ā | SSG (with generateStaticParams) | At build time |
| Ī» | Dynamic/Server-rendered | Per request |
| ā | Partial Prerender | Shell at build, dynamic at request |
Size Columns Explained
- Size: JavaScript specific to that route
- First Load JS: Total JS needed for that route = Shared + Size
The shared bundle loads once and is cached. Route-specific code loads per-navigation.
Identifying Issues
Large First Load JS
ā Ī» /dashboard 45.8 kB 129.7 kB ā ļø
Red flag: First Load JS > 100 kB. Investigate:
- Heavy dependencies in client bundle?
- Can some components be Server Components?
- Can you lazy load parts?
Unexpected Dynamic Routes
ā Ī» /about 2.1 kB 86.0 kB ā ļø
Why is /about dynamic? Check for:
cookies()orheaders()usagesearchParamsaccesscache: 'no-store'in fetchesdynamic = 'force-dynamic'export
The .next Directory
.next/
āāā cache/ # Build and fetch cache
āāā server/
ā āāā app/ # Server-rendered pages
ā āāā chunks/ # Server-side chunks
āāā static/
ā āāā chunks/ # Client-side chunks (what users download)
āāā BUILD_ID # Unique build identifier
Analyzing Bundle Contents
Use the bundle analyzer:
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({})
ANALYZE=true npm run build
Pre-Generated Pages
The build output shows which paths are pre-generated:
ā ā /blog/[slug] 3.4 kB 87.3 kB
ā ā /blog/hello-world
ā ā /blog/nextjs-guide
ā ā /blog/react-tips
ā ā [+8 more paths]
These 11 pages are HTML files ready to serve instantly.
Static Export Check
For fully static deployments:
// next.config.js
module.exports = {
output: 'export',
}
Build fails if you have dynamic routes:
Error: Page "/dashboard" is using cookies(). Static generation
is not supported for pages using Request APIs.
Optimization Checklist
- All marketing pages should be ā (static)
- First Load JS under 100 kB per route
- Shared bundle under 90 kB
- Dynamic routes (Ī») only where truly needed
- ISR pages (ā) have reasonable revalidation times
Summary
- Build output shows what each route generates
- Symbols indicate rendering strategy: ā static, ā SSG, Ī» dynamic
- First Load JS = shared bundle + route-specific code
- Investigate unexpected dynamic routes
- Use bundle analyzer for detailed JS breakdown
- Aim for static (ā) pages wherever possible

