Two small config options, one real operational lever
A recent Next.js 16.3 canary build stabilized two configuration options that had been sitting behind experimental flags: outputHashSalt and immutable static asset output. Neither is a headline feature — there's no new UI, no new API route type — but both sit directly on top of a problem every production Next.js deployment eventually runs into: cache invalidation for build output.
What each option actually does
Next.js builds static assets — JS chunks, CSS, and other build output — with content hashes baked into the filename, which is what makes long-lived, aggressive caching safe in the first place: the filename changes when the content changes, so a far-future Cache-Control header never serves stale content.
outputHashSalt lets you seed the value that feeds into those content hashes. In effect, it gives you a way to force a new set of asset hashes across a rebuild without changing a single line of application code. That sounds like a narrow use case until you hit one of these situations:
- You need to force cache invalidation across your CDN and every client's browser cache after a caching-related incident, without shipping an unrelated code change to trigger it.
- You're running the same commit across two environments (say, a canary and a stable rollout) and need their asset hashes to differ so they don't collide in a shared CDN cache.
Immutable static assets, now stable, lets the framework mark eligible build output with cache semantics that tell CDNs and browsers "this exact file will never change — stop revalidating it." Paired with the hash-based filenames Next.js already produces, this is a fairly safe default: the browser or CDN can skip conditional requests (If-None-Match, 304 round trips) entirely for these assets, since a content change always produces a new filename anyway.
Configuring it
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
outputHashSalt: process.env.BUILD_HASH_SALT ?? "default",
experimental: {
// Check current Next.js docs for the exact flag name at
// your installed version — this stabilized recently and
// the config surface may still be settling.
immutableStaticAssets: true,
},
};
export default nextConfig;The important detail is where BUILD_HASH_SALT comes from. Treat it as a deploy-pipeline-controlled value, not something each build server generates on its own:
# .github/workflows/deploy.yml (excerpt)
- name: Build
env:
BUILD_HASH_SALT: ${{ github.sha }}
run: npm run buildUsing the commit SHA as the salt gives you a value that's stable across every instance building the same release, but changes automatically on every new commit — which is exactly the property you want.
The mistake this setting makes possible
If you set outputHashSalt to something that varies per build invocation — a timestamp, a random value generated at build time, anything that isn't pinned to the release — you introduce a subtle rollout bug: different instances behind your load balancer building "the same" release end up producing different asset hashes. A client that loads the HTML shell from instance A and then requests a chunk that only exists under instance B's hash gets a 404 mid-session, typically surfacing as a confusing "failed to load chunk" error that seems to happen at random.
This isn't a new failure mode — asset hash mismatches during rolling deploys predate this config option — but outputHashSalt makes it easier to introduce by accident if you don't pin the value deliberately. The fix is straightforward: derive the salt from something that is identical across every build of the same release (a commit SHA, a release tag, a CI build ID tied to that specific artifact) and pass it explicitly rather than relying on framework defaults during a multi-instance build.
Where this actually earns its keep
- CDN cost and latency. Fewer revalidation round trips means fewer origin requests and slightly better perceived load time for repeat visitors — a real, if modest, win at scale.
- Incident response. Being able to force a clean cache-bust via a pipeline variable, without needing an unrelated code change, is a small but genuinely useful tool to have on hand during a caching-related incident.
- Multi-environment builds. Teams running canary and stable tracks from the same commit can deliberately differentiate their asset hashes instead of relying on incidental differences.
Takeaways
Neither of these options changes how you write application code, and that's exactly why they're easy to overlook. But caching correctness is one of those areas where a small, well-understood config lever beats an ad hoc workaround during an incident. If you're on a Next.js 16.3 canary or planning your upgrade path, it's worth wiring outputHashSalt to your CI pipeline now — before you need it during an incident, not during one. At webhani, we treat caching configuration as part of the deploy pipeline review, not an afterthought bolted on after a production issue.
References: Next.js — Vercel — releases.sh, Next.js 16.3: Instant Navigations (Next.js Blog)