Next.js Cheatsheet
Environment and Config
Use this Next.js reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Environment Variable Files
Next.js loads .env files automatically. Files are loaded in this priority order (higher overrides lower):
| File | When loaded | Committed? |
|---|---|---|
.env.local | Always | No (gitignore) |
.env.development.local | npm run dev only | No |
.env.test.local | NODE_ENV=test only | No |
.env.production.local | npm run build/start | No |
.env.development | npm run dev only | Yes |
.env.test | NODE_ENV=test only | Yes |
.env.production | npm run build/start | Yes |
.env | All environments | Yes |
.env.localis always the highest priority and is never committed. Use it for secrets.
Server vs. Browser Variables
# .env.local # Server-only (NOT sent to browser) DATABASE_URL=postgresql://... JWT_SECRET=supersecretkey STRIPE_SECRET_KEY=sk_live_... # Browser-visible (must prefix with NEXT_PUBLIC_) NEXT_PUBLIC_APP_URL=https://myapp.com NEXT_PUBLIC_STRIPE_PUBLIC_KEY=pk_live_...
// Server Component / Route Handler / Middleware const secret = process.env.JWT_SECRET // works // Client Component const appUrl = process.env.NEXT_PUBLIC_APP_URL // works const secret = process.env.JWT_SECRET // undefined (never sent to client)
NEXT_PUBLIC_variables are inlined at build time — they are hardcoded into the JS bundle. Do not put secrets here.
Accessing Variables
// Server Component export default async function Page() { const db = process.env.DATABASE_URL! // string | undefined return <div>DB configured: {!!db}</div> }
// Client Component 'use client' export function Footer() { return <p>{process.env.NEXT_PUBLIC_APP_URL}</p> }
Type Safety for Env Vars
Use a validation file loaded at startup:
// lib/env.ts (import this in layout.tsx or server entry) import { z } from 'zod' const envSchema = z.object({ DATABASE_URL: z.string().url(), JWT_SECRET: z.string().min(32), NEXT_PUBLIC_APP_URL: z.string().url(), NODE_ENV: z.enum(['development', 'test', 'production']), }) export const env = envSchema.parse(process.env)
Or use the @t3-oss/env-nextjs package for a typed, validated env with NEXT_PUBLIC_ support.
next.config.ts — Full Reference
import type { NextConfig } from 'next' const config: NextConfig = { // ─── Output / Build ─────────────────────────────────────────── output: 'standalone', // self-contained output (Docker) // output: 'export', // static HTML export (no server needed) distDir: '.next', // custom build output directory // ─── Images ─────────────────────────────────────────────────── images: { remotePatterns: [ { protocol: 'https', hostname: 'example.com' }, ], formats: ['image/avif', 'image/webp'], // default: webp only minimumCacheTTL: 60, // seconds deviceSizes: [640, 750, 828, 1080, 1200, 1920], imageSizes: [16, 32, 48, 64, 96, 128, 256, 384], unoptimized: false, }, // ─── Redirects / Rewrites / Headers ────────────────────────── async redirects() { return [ { source: '/old/:slug', destination: '/new/:slug', permanent: true }, ] }, async rewrites() { return { beforeFiles: [], // checked before filesystem/pages afterFiles: [], // checked after filesystem, before fallbacks fallback: [], // last resort } }, async headers() { return [ { source: '/(.*)', headers: [ { key: 'X-Frame-Options', value: 'DENY' }, { key: 'X-Content-Type-Options', value: 'nosniff' }, ], }, ] }, // ─── TypeScript / ESLint ────────────────────────────────────── typescript: { ignoreBuildErrors: false, // true to skip type errors at build (dangerous) }, eslint: { ignoreDuringBuilds: false, dirs: ['app', 'components', 'lib'], }, // ─── Env ────────────────────────────────────────────────────── env: { // Expose build-time constants (accessible as process.env.VAR anywhere) BUILD_TIME: new Date().toISOString(), }, // ─── Webpack customization ──────────────────────────────────── webpack(config, { isServer }) { if (!isServer) { // Don't bundle server-only packages client-side config.resolve.fallback = { fs: false, path: false } } return config }, // ─── React ─────────────────────────────────────────────────── reactStrictMode: true, reactCompiler: true, // React Compiler (auto-memo) — stable top-level in Next 16 // ─── Compiler ──────────────────────────────────────────────── compiler: { removeConsole: process.env.NODE_ENV === 'production', // styledComponents: true, // emotion: true, }, // ─── Server packages ───────────────────────────────────────── // Opt packages out of Server Component bundling (use require at runtime). // Top-level since Next 15 (was experimental.serverComponentsExternalPackages). serverExternalPackages: ['@prisma/client'], // ─── Cache Components (Next 16, beta) ──────────────────────── // Enables the 'use cache' directive + cacheLife/cacheTag. // Replaces the Next 15 experimental ppr / dynamicIO flags. // cacheComponents: true, // ─── Logging ───────────────────────────────────────────────── logging: { fetches: { fullUrl: true, // log full URLs in dev }, }, // ─── Page Extensions ───────────────────────────────────────── pageExtensions: ['tsx', 'ts', 'jsx', 'js'], // default // ─── Trailing Slash ────────────────────────────────────────── trailingSlash: false, // true → /about/ instead of /about // ─── Base Path ─────────────────────────────────────────────── // basePath: '/docs', // serve the app under a sub-path } export default config
Static Export (output: 'export')
Generates a fully static site — no Node.js server needed.
// next.config.ts export default { output: 'export' }
Limitations:
- No Server Actions (no server)
- No Route Handlers at runtime
- No ISR (static only)
- No next/image optimization (use unoptimized: true)
- No middleware
npm run build # generates out/ directory npx serve out # serve locally to test
Standalone Output (Docker)
export default { output: 'standalone' }
FROM node:20-alpine COPY .next/standalone ./ COPY .next/static ./.next/static COPY public ./public CMD node server.js
instrumentation.ts — Observability Hook
Stable since Next 15 — just create the file, no config flag needed (the old experimental.instrumentationHook is gone).
// instrumentation.ts (root level) export async function register() { if (process.env.NEXT_RUNTIME === 'nodejs') { // Node.js-only setup (OpenTelemetry, Sentry, etc.) const { NodeSDK } = await import('@opentelemetry/sdk-node') new NodeSDK().start() } } export async function onRequestError( err: Error, request: { path: string; method: string }, context: { routerKind: string; routePath: string } ) { // Report errors to your monitoring service await reportError(err, { path: request.path }) }
tsconfig.json Key Settings for Next.js
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": { "@/*": ["./*"] }
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}Environment Variable Gotchas
| Gotcha | Detail |
|---|---|
NEXT_PUBLIC_ vars are baked in at build | Changing them requires a rebuild |
| Dynamic runtime env vars | Use process.env.VAR in Route Handlers/Server Actions — not in layout/page module scope |
.env.local not in Docker | Mount it as a volume or use Docker secrets / CI env |
undefined in browser | Any var without NEXT_PUBLIC_ is undefined in client code |
next.config.ts env key | Exposes vars to both server and client — use only for non-secret build constants |