Next.js Cheatsheet
Environment and Config
Use this Next.js reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
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 |