new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Next.js · API reference · all subjects

config

577 notes in this subject, read out of this brain and free to use. This is page 2 of 10.

deploymentId behavior in Next.js

When a deploymentId is configured, Next.js performs the following: 1) Appends ?dpl=<deploymentId> to static asset URLs (JavaScript, CSS, images), 2) Adds an x-deployment-id header to client-side navigation requests, 3) Adds an x-nextjs-deployment-id header to navigation responses, 4) Injects a data-dpl-id attribute on the <html> element, 5) Includes the deploymentId in the 'use cache' cache key, invalidating cache entries when the deployment ID changes.

NEXT_DEPLOYMENT_ID environment variable

You can set the deployment ID using the NEXT_DEPLOYMENT_ID environment variable. Example: NEXT_DEPLOYMENT_ID=my-deployment-id next build. If both the deploymentId option in next.config.js and the NEXT_DEPLOYMENT_ID environment variable are set, the deploymentId value in next.config.js takes precedence.

deploymentId config option

The deploymentId option allows you to set an identifier for your deployment. This identifier is used for version skew protection and cache busting during rolling deployments. It can be configured in next.config.js as: module.exports = { deploymentId: 'my-deployment-id', }

distDir must not leave project directory

The distDir option must be a relative path within the project directory. Paths like ../build that would escape the project directory are invalid and will not work.

distDir config option

The distDir config option in next.config.js specifies a custom build directory to use instead of the default .next directory. For example, setting distDir: 'build' will cause next build to output to a build folder instead of .next.

Cannot destructure process.env with env config

Destructuring process.env variables does not work when environment variables are specified through next.config.js env config, due to the nature of webpack's DefinePlugin. Variables must be accessed individually as process.env.variableName.

env config is legacy approach

The env configuration in next.config.js is a legacy approach. Since Next.js 9.4, a more intuitive and ergonomic experience for adding environment variables is available through environment variables and .env files.

Environment variables replaced at build time

Next.js replaces process.env variables with their values at build time using webpack's DefinePlugin. The replacement is literal - for example, process.env.customKey with value 'my-value' becomes the string literal 'my-value' in the compiled output.

env config syntax and usage

In next.config.js, use module.exports = { env: { customKey: 'my-value' } } to define environment variables. These variables are then accessible as process.env.customKey in your application code. For example, env: { customKey: 'my-value' } allows access to process.env.customKey.

env config in next.config.js

The env configuration in next.config.js allows you to add environment variables to the JavaScript bundle at build time. Environment variables specified this way will always be included in the bundle. The NEXT_PUBLIC_ prefix only has an effect when specifying variables through the environment or .env files, not through next.config.js.

expireTime and Cache-Control header calculation

When expireTime is configured, the Cache-Control header is calculated based on the specific revalidate period. The stale-while-revalidate value is set to the difference between the expireTime and the revalidate period. For example, with a revalidate of 15 minutes (900 seconds) and an expireTime of one hour (3600 seconds), the generated Cache-Control header is s-maxage=900, stale-while-revalidate=2700, allowing the content to stay stale for 15 minutes less than the configured expire time.

expireTime config option

The expireTime config option in next.config.js specifies a custom stale-while-revalidate expire time for CDNs to consume in the Cache-Control header for ISR enabled pages. It is defined as a number representing seconds. For example, expireTime: 3600 sets the expire time to one hour.

expireTime configuration example

module.exports = { // one hour in seconds expireTime: 3600, }

When to use cssChunking 'strict' strategy

In webpack, switch from the default true to 'strict' if you run into unexpected CSS behavior. For example, if you import a.css and b.css in different files using different import orders, 'strict' prevents merging and loads them in import order if b.css depends on a.css, at the cost of more chunks and requests.

How requestCost affects CSS merging in graph algorithm

requestCost is the price of a request in bytes. Raise requestCost and the graph algorithm merges more CSS, trading larger downloads for fewer requests. Lower requestCost toward 0 and it splits chunks apart, so routes download closer to only what they import but make more requests.

Graph algorithm CSS merging decision process

The graph algorithm decides whether to merge CSS into shared chunks by calculating whether the cost of an additional request is outweighed by the bytes of unused CSS a route would download if merged. For example, if two routes share a stylesheet and only one imports an additional stylesheet, the algorithm keeps the additional stylesheet merged while it stays under requestCost, and splits it out once it grows large enough to outweigh the request.

Sources of unused CSS in routes

Unused CSS comes from two sources. Either it is dead CSS in a stylesheet your route imports (fix by removing unused rules or moving them to stylesheets only used routes import; CSS Modules make this natural by scoping styles). Or the bundler merged another stylesheet into a shared chunk that your route loads (governed by the chunking strategy).

Debugging unused CSS in routes

To check what CSS a route actually uses, use Lighthouse to flag 'Reduce unused CSS' opportunities with estimated savings, and use Chrome DevTools Coverage panel which shows each stylesheet's applied CSS in green and unused CSS in gray. Watch out for styles that only apply on interaction like :hover, :focus, or classes toggled by JavaScript for menus and modals, since Coverage counts them as unused until triggered.

cssChunking configuration example with true

To enable CSS chunking with the default merging strategy, configure next.config.js or next.config.ts with: experimental: { cssChunking: true } This is the default and works with both webpack and Turbopack.

When to use cssChunking false

Use cssChunking false to disable merging entirely in webpack when you need to avoid automatic CSS reordering or merging.

When to use cssChunking 'graph' strategy

In Turbopack, switch from the default true to 'graph' to tune how CSS is shared across routes, cutting the unused CSS a route downloads at the cost of more requests. This is usually a performance consideration.

cssChunking 'graph' behavior

When cssChunking is set to 'graph', Next.js uses a cost-based graph algorithm to group CSS across routes, balancing the bytes each route downloads and the requests it makes. This option is Turbopack-only.

cssChunking graph configuration with requestCost and weightDistribution

The cssChunking graph strategy can be configured with an object containing type: 'graph', requestCost, and weightDistribution properties. requestCost (default 20000) is the estimated cost in bytes of each additional CSS request. weightDistribution (default 0.1) controls how a shared chunk's cost is distributed across routes that load it, weighted by how much CSS each route imports.

cssChunking false behavior

When cssChunking is false, Next.js will not attempt to merge or re-order CSS files. This option is webpack-only.

cssChunking true (default) behavior

When cssChunking is true (the default), Next.js tries to merge CSS files whenever possible, determining explicit and implicit dependencies between files from import order to reduce the number of chunks and requests. This option works with both webpack and Turbopack.

cssChunking configuration option

The cssChunking option in experimental configuration controls how CSS files are chunked in a Next.js application. It can be set to true (default), false, 'strict', or 'graph' (object form with type, requestCost, and weightDistribution properties). This strategy improves performance by splitting and reordering CSS files so a route loads close to only the CSS it needs.

weightDistribution parameter behavior

The weightDistribution parameter in the graph chunking strategy controls how a shared chunk's cost is distributed across routes that load it, weighted by how much CSS each route imports. A value of 0 weights every route equally. Higher values give more weight to routes that import less CSS, so the algorithm prioritizes routes with less CSS, assuming extra CSS is less noticeable on large routes.

How weightDistribution affects CSS merging in graph algorithm

weightDistribution decides how much the algorithm cares about routes that download CSS they never imported. At 0, all routes count equally. Raise weightDistribution and routes that import little CSS count for more, so the algorithm works to spare them unused CSS at the cost of more requests overall.

cssChunking configuration example with graph object

To use the graph strategy with custom tuning in Turbopack, configure next.config.js or next.config.ts with: experimental: { cssChunking: { type: 'graph', requestCost: 100000, weightDistribution: 0.1, }, } Both requestCost and weightDistribution are optional.

cssChunking 'strict' behavior

When cssChunking is set to 'strict', Next.js will load CSS files in the correct order they are imported into files, which can lead to more chunks and requests. This option is webpack-only.

requestCost parameter behavior

The requestCost parameter in the graph chunking strategy represents the estimated cost in bytes of each additional CSS request. Larger values bias toward fewer, larger shared chunks and fewer requests overall. The algorithm keeps CSS merged while it stays under requestCost, and splits it out once it grows large enough to outweigh the request.

Graph algorithm overview

The graph algorithm works with individual CSS files. It starts from the ordered list of CSS each route imports, builds a weighted graph where two CSS files get a heavier edge the more routes import them together in the same order, then flattens that graph into a single line keeping frequently-paired files adjacent, and places cuts dividing the line into chunks. A route loads every chunk holding a file it imports. The algorithm chooses where to split chunks to minimize total download cost across all routes, balancing bytes and requests.

generateBuildId example

Example of using generateBuildId in next.config.js: module.exports = { generateBuildId: async () => { // This could be anything, using the latest git hash return process.env.GIT_HASH }, }

generateBuildId config option

The generateBuildId configuration option in next.config.js allows you to define a custom build ID that identifies which version of your application is being served. Next.js generates an ID during 'next build' by default. Use generateBuildId when rebuilding for each stage of your environment to generate a consistent build ID for use between containers. The function must be async and should return a string value that identifies the build.

Build ID purpose and consistency

The build ID generated during 'next build' is used to identify which version of your application is being served. The same build should be used and boot up multiple containers. A consistent build ID is required when rebuilding for each stage of your environment across different containers.

generateEtags config option

The generateEtags option in next.config.js controls whether Next.js generates ETags for every page. By default, generateEtags is true and Next.js will generate ETags for every page. You can disable etag generation by setting generateEtags to false in next.config.js, which may be desirable depending on your cache strategy.

generateEtags configuration example

To disable etag generation in Next.js, add the following configuration to next.config.js: module.exports = { generateEtags: false, }

exportPathMap is deprecated

exportPathMap is a legacy feature exclusive to next export and is currently deprecated in favor of getStaticPaths with pages or generateStaticParams with app. Using exportPathMap is no longer recommended.

next export output directory customization

next export uses out as the default output directory. This can be customized using the -o argument: next export -o outdir

exportPathMap async function signature

exportPathMap is an async function that accepts two arguments: defaultPathMap (the default map used by Next.js) and an options object containing dev, dir, outDir, distDir, and buildId.

trailingSlash config for exportPathMap

To export pages as index.html files and require trailing slashes (so /about becomes /about/index.html), enable trailingSlash config in next.config.js: module.exports = { trailingSlash: true }

exportPathMap function parameters

The options object passed to exportPathMap contains: dev (boolean - true in development, false when running next export), dir (absolute path to project directory), outDir (absolute path to out/ directory, null when dev is true), distDir (absolute path to .next/ directory), and buildId (the generated build id).

exportPathMap deprecated warning with getStaticPaths

Using exportPathMap is deprecated and is overridden by getStaticPaths inside pages. It is not recommended to use them together.

exportPathMap query field limitation

The query field in exportPathMap cannot be used with automatically statically optimized pages or getStaticProps pages, because they are rendered to HTML files at build-time and additional query information cannot be provided during next export.

exportPathMap with next dev

Paths defined in exportPathMap are available when using next dev. In development, exportPathMap is used to define routes.

exportPathMap return value structure

exportPathMap returns an object where keys are pathnames and values are objects with two fields: page (String - the page inside pages directory to render) and query (Object - the query object passed to getInitialProps when prerendering, defaults to {}).

exportPathMap example configuration

Example exportPathMap configuration in next.config.js: module.exports = { exportPathMap: async function (defaultPathMap, { dev, dir, outDir, distDir, buildId }) { return { '/': { page: '/' }, '/about': { page: '/about' }, '/p/hello-nextjs': { page: '/post', query: { title: 'hello-nextjs' } }, '/p/learn-nextjs': { page: '/post', query: { title: 'learn-nextjs' } }, '/p/deploy-nextjs': { page: '/post', query: { title: 'deploy-nextjs' } }, } } }

exportPathMap pathname can be filename

The exported pathname can be a filename (for example, /readme.md), though you may need to set the Content-Type header to text/html when serving its content if it is different than .html.

cacheHandlers config option

The `cacheHandlers` configuration in next.config.ts or next.config.js allows you to define custom cache storage implementations for 'use cache' and 'use cache: remote' directives. 'use cache: private' is not configurable. Configuration is an object with optional `default` and `remote` properties, each taking a file path resolved via require.resolve(). Example: { cacheHandlers: { default: require.resolve('./cache-handlers/default-handler.js'), remote: require.resolve('./cache-handlers/remote-handler.js') } }

CacheHandler getExpiration() method

The getExpiration() method gets the maximum revalidation timestamp for a set of tags. Signature: getExpiration(tags: string[]): Promise<number>. Parameter: tags is a string array of tags to check expiration for. Returns: 0 if none of the tags were ever revalidated, a timestamp in milliseconds representing the most recent revalidation, or Infinity to indicate soft tags should be checked in the get() method instead. If not tracking tag revalidation timestamps, return 0. Otherwise, find the most recent revalidation timestamp across all provided tags.

CacheHandler updateTags() method

The updateTags() method is called when tags are revalidated or expired. Signature: updateTags(tags: string[], durations?: { expire?: number }): Promise<void>. Parameters: tags is a string array of tags to update; durations is an optional object with an optional expire property (number in seconds). Returns Promise<void>. The handler should update its internal state to mark these tags as invalidated. Iterate through the cache and remove entries whose tags match the provided list.

Basic in-memory cache handler example

const cache = new Map() const pendingSets = new Map() module.exports = { async get(cacheKey, softTags) { const pendingPromise = pendingSets.get(cacheKey) if (pendingPromise) { await pendingPromise } const entry = cache.get(cacheKey) if (!entry) { return undefined } const now = Date.now() if (now > entry.timestamp + entry.revalidate * 1000) { return undefined } return entry }, async set(cacheKey, pendingEntry) { let resolvePending const pendingPromise = new Promise((resolve) => { resolvePending = resolve }) pendingSets.set(cacheKey, pendingPromise) try { const entry = await pendingEntry cache.set(cacheKey, entry) } finally { resolvePending() pendingSets.delete(cacheKey) } }, async refreshTags() { }, async getExpiration(tags) { return 0 }, async updateTags(tags, durations) { for (const [key, entry] of cache.entries()) { if (entry.tags.some((tag) => tags.includes(tag))) { cache.delete(key) } } }, }

CacheHandler get() method

The get() method retrieves a cache entry for a given cache key. Signature: get(cacheKey: string, softTags: string[]): Promise<CacheEntry | undefined>. Parameters: cacheKey is a string with the unique key for the cache entry; softTags is a string array of implicit tags derived from the route path. Returns a CacheEntry object if found, or undefined if not found or expired. The handler should retrieve the cache entry from storage, check if it has expired based on the revalidate time, and return undefined for missing or expired entries.

CacheHandler set() method

The set() method stores a cache entry for a given cache key. Signature: set(cacheKey: string, pendingEntry: Promise<CacheEntry>): Promise<void>. Parameters: cacheKey is a string for the unique key to store the entry under; pendingEntry is a Promise<CacheEntry> that may still be pending when called. The handler must await the pendingEntry promise before storing it, since the cache entry may still be generating. Once resolved, the entry should be stored in the cache system.

CacheHandler refreshTags() method

The refreshTags() method is called periodically before starting a new request to sync with external tag services. Signature: refreshTags(): Promise<void>. Returns Promise<void>. This is useful for coordinating cache invalidation across multiple instances or services. For in-memory caches, this can be a no-op. For distributed caches, use this to sync tag state from an external service or database before processing requests.

When custom cache handlers are needed

Most applications do not need custom cache handlers. The default in-memory cache works well for typical use cases. Custom handlers are for advanced scenarios: sharing cache across multiple instances by integrating with shared storage systems like Redis, Memcached, or DynamoDB; or changing storage type to disk, database, or external services for persistence, reduced memory usage, or infrastructure integration.

Handler types in cacheHandlers

Two handler types can be configured: `default` is used by the 'use cache' directive, and `remote` is used by the 'use cache: remote' directive. If cacheHandlers is not configured, Next.js uses an in-memory LRU (Least Recently Used) cache for both default and remote. Additional named handlers (e.g., 'sessions', 'analytics') can be defined and referenced with 'use cache: <name>'.

CacheEntry type structure

The CacheEntry object has this structure: { value: ReadableStream<Uint8Array>, tags: string[], stale: number, timestamp: number, expire: number, revalidate: number }. Property details: value is the cached data as a ReadableStream; tags is an array of cache tags excluding soft tags; stale is the duration in seconds for client-side staleness; timestamp is when the entry was created in milliseconds; expire is how long the entry is allowed to be used in seconds; revalidate is how long until the entry should be revalidated in seconds.

Redis cache handler example

const { createClient } = require('redis') const client = createClient({ url: process.env.REDIS_URL }) client.connect() module.exports = { async get(cacheKey, softTags) { const stored = await client.get(cacheKey) if (!stored) return undefined const data = JSON.parse(stored) return { value: new ReadableStream({ start(controller) { controller.enqueue(Buffer.from(data.value, 'base64')) controller.close() }, }), tags: data.tags, stale: data.stale, timestamp: data.timestamp, expire: data.expire, revalidate: data.revalidate, } }, async set(cacheKey, pendingEntry) { const entry = await pendingEntry const reader = entry.value.getReader() const chunks = [] try { while (true) { const { done, value } = await reader.read() if (done) break chunks.push(value) } } finally { reader.releaseLock() } const data = Buffer.concat(chunks.map((chunk) => Buffer.from(chunk))) await client.set( cacheKey, JSON.stringify({ value: data.toString('base64'), tags: entry.tags, stale: entry.stale, timestamp: entry.timestamp, expire: entry.expire, revalidate: entry.revalidate, }), { EX: entry.expire } ) }, async refreshTags() { }, async getExpiration(tags) { return 0 }, async updateTags(tags, durations) { }, }

Distributed tag coordination with Redis

const { createClient } = require('redis') const client = createClient({ url: process.env.REDIS_URL }) client.connect() const localTagTimestamps = new Map() module.exports = { async refreshTags() { const tagKeys = await client.sMembers('revalidated-tags') if (tagKeys.length > 0) { const values = await client.mGet(tagKeys.map((k) => `tag:${k}`)) for (let i = 0; i < tagKeys.length; i++) { localTagTimestamps.set(tagKeys[i], Number(values[i])) } } }, async getExpiration(tags) { const timestamps = tags.map((tag) => localTagTimestamps.get(tag) || 0) return Math.max(...timestamps, 0) }, async updateTags(tags, durations) { const now = Date.now() const pipeline = client.multi() for (const tag of tags) { pipeline.set(`tag:${tag}`, String(now)) pipeline.sAdd('revalidated-tags', tag) localTagTimestamps.set(tag, now) } await pipeline.exec() }, }

Give your agent this brain