next.config and Build Configuration
IntermediateOne file controls redirects, headers, image hosts, rewrites and the experimental flags. Most of what belongs here is infrastructure, and most of what does not is business logic.
Overview
Almost every Next application needs a handful of the same configuration: which remote image hosts are allowed, which old URLs redirect where, what security headers to send, and occasionally a rewrite that proxies an API. Beyond that the file collects experimental flags, which is where it becomes risky — a flag copied from a blog post can change caching or rendering behaviour in ways that only appear in production. The discipline is to keep it small, comment why each entry exists, and treat an experimental flag as a decision rather than a default.
What Belongs Here
The entries nearly every project ends up with.
// next.config.js
/** @type {import('next').NextConfig} */
module.exports = {
reactStrictMode: true,
images: {
remotePatterns: [
{ protocol: 'https', hostname: '**.supabase.co', pathname: '/storage/**' },
],
formats: ['image/avif', 'image/webp'],
},
async redirects() {
return [
// moved 2026-08 — keep until the old links stop appearing in logs
{ source: '/old-problems', destination: '/dsa/problems', permanent: true },
]
},
async headers() {
return [{ source: '/:path*', headers: SECURITY_HEADERS }]
},
async rewrites() {
// proxy the API so cookies stay first-party
return [{ source: '/api/:path*', destination: `${process.env.API_URL}/:path*` }]
},
// Trim large barrel-file libraries
experimental: { optimizePackageImports: ['lucide-react', 'date-fns'] },
}
// redirect — the URL changes, the browser sees the new one (301/308)
// rewrite — the URL stays, different content is served (a proxy)What Does Not
Two settings that hide failures, and one that surprises.
// Never ship these:
typescript: { ignoreBuildErrors: true }
eslint: { ignoreDuringBuilds: true }
// They convert a build-time failure into a runtime one. If the build
// is slow, run tsc and eslint as separate CI steps — do not silence.
// Experimental flags change real behaviour. Each one is a decision:
experimental: {
ppr: 'incremental', // changes how routes render
staleTimes: { dynamic: 30 }, // changes client cache freshness
serverActions: { bodySizeLimit: '2mb' },
}
// Write down why each is enabled. A flag nobody can explain is a flag
// that will be blamed for the next unexplained bug.
// trailingSlash changes every URL you have — decide once, early:
trailingSlash: false // /problems (the default)
// Changing it later means redirecting every indexed URL.
// basePath, if the app is served under a subpath — it affects every
// link, asset and API route:
basePath: '/app'
// And keep business logic out. A redirect that depends on the user
// belongs in middleware; one that depends on data belongs in a page.Environment-Aware Config
Differing by environment without duplicating the file.
const isProd = process.env.NODE_ENV === 'production'
const isPreview = process.env.VERCEL_ENV === 'preview'
module.exports = {
// keep source maps for the error tracker but do not serve them
productionBrowserSourceMaps: false,
// block preview deployments from search engines
async headers() {
return isPreview
? [{ source: '/:path*', headers: [{ key: 'X-Robots-Tag', value: 'noindex' }] }]
: [{ source: '/:path*', headers: SECURITY_HEADERS }]
},
compiler: {
removeConsole: isProd ? { exclude: ['error', 'warn'] } : false,
},
}
// The noindex on previews matters: a preview URL indexed by Google
// competes with production for the same content.
// The config runs at BUILD time, so process.env here is the build
// environment — not the runtime one. A value read here is frozen into
// the build, exactly like NEXT_PUBLIC_.
// Changing this file requires a rebuild and a redeploy. Anything that
// must change without one belongs in a server-read environment
// variable or a database row.Key Points to Remember
- 1Image hosts, redirects, security headers and rewrites are what nearly every project configures here
- 2A redirect changes the URL; a rewrite keeps it and serves different content
- 3ignoreBuildErrors and ignoreDuringBuilds convert build failures into runtime failures — never ship them
- 4Every experimental flag changes behaviour, so record why it is enabled
- 5The config is evaluated at build time, so values read from the environment are frozen into the build
Interview Questions
Sign in to ask AriaWhat is the difference between a redirect and a rewrite?
Why is ignoreBuildErrors dangerous?
Why should preview deployments send X-Robots-Tag: noindex?
Ask Aria about next.config and Build Configuration
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.