Next.js 16 reads next.config.ts natively. You get auto-completion and type-checking on every option.
1import type { NextConfig } from "next";
2
3const config: NextConfig = {
4 reactStrictMode: true,
5 images: {
6 remotePatterns: [
7 { protocol: "https", hostname: "images.unsplash.com" },
8 { protocol: "https", hostname: "cdn.coachnest.dev" },
9 ],
10 },
11 experimental: {
12 ppr: "incremental", // Partial Prerendering, opt-in per route
13 typedRoutes: true, // Type-safe <Link href> across the app
14 serverActions: { bodySizeLimit: "2mb" },
15 },
16 async redirects() {
17 return [{ source: "/docs", destination: "/learn", permanent: true }];
18 },
19};
20
21export default config;Use
satisfies NextConfigif you'd rather keep literal types narrow.
The CLI scaffolds a solid default. Two settings worth turning on once you're comfortable:
1{
2 "compilerOptions": {
3 "strict": true,
4 "noUncheckedIndexedAccess": true, // safer array & object access
5 "noFallthroughCasesInSwitch": true,
6 "moduleResolution": "bundler",
7 "paths": {
8 "@/*": ["./*"]
9 }
10 }
11}Path aliases work everywhere — components, server actions, route handlers.
With experimental.typedRoutes: true:
1import Link from "next/link";
2
3// ✅ Valid
4<Link href="/dashboard" />
5<Link href={`/courses/${slug}`} />
6
7// ❌ Compile error — no such route
8<Link href="/dashbaord" />Catches typos at build time. Worth the opt-in.
1npm run lintThe eslint-config-next preset enforces:
next/image, next/link, next/script<a href> for internal nav<img> when next/image worksReact.lazy for routes (use the App Router instead)| File | Loaded in |
|---|---|
.env | All environments |
.env.local | All environments — gitignored |
.env.development | next dev |
.env.production | next build / next start |
1# Only available on the server (default)
2DATABASE_URL=postgresql://...
3
4# Exposed to the browser — prefix with NEXT_PUBLIC_
5NEXT_PUBLIC_POSTHOG_KEY=phc_abc123Security: never put secrets behind
NEXT_PUBLIC_. The variable is inlined into the JS bundle and visible in DevTools.
1// lib/env.ts
2import { z } from "zod";
3
4const schema = z.object({
5 DATABASE_URL: z.string().url(),
6 NEXTAUTH_SECRET: z.string().min(32),
7 NEXT_PUBLIC_APP_URL: z.string().url(),
8});
9
10export const env = schema.parse(process.env);Crash early on misconfigured deploys instead of returning HTTP 500s in production.
1{
2 "scripts": {
3 "dev": "next dev",
4 "build": "next build",
5 "start": "next start",
6 "lint": "next lint",
7 "typecheck": "tsc --noEmit",
8 "test": "vitest",
9 "e2e": "playwright test"
10 }
11}Run npm run typecheck in CI — next build reports type errors, but a dedicated step keeps PR feedback fast.