How to Fix: Output chunks change when build stays the same
Your Next.js build is not actually changing, but the emitted chunk filenames and related manifest output still drift between builds. That usually points to non-deterministic module or chunk ordering inside the bundling pipeline, not to a real source change. In practice, this happens when the build graph is stable at the application level, but the bundler assigns different internal IDs or chunk group ordering across runs, which then changes hashed output names and the generated build manifest.
Table of Contents
Understanding the Root Cause
In a healthy production build, identical inputs should produce identical outputs. When that does not happen, the most common cause is that the underlying webpack chunk graph is being generated in a way that is not fully deterministic for your specific dependency tree or framework version.
With Next.js, the final output is influenced by several layers:
- Route-level code splitting
- Webpack module ID assignment
- Chunk ID generation
- Build manifest serialization
- Node.js, package manager, and lockfile resolution
If any of those layers produce a different ordering across builds, the resulting chunk hashes can change even when the source files do not. That is why you may see different files in .next/static/chunks or changes in the manifest after repeated npm run build executions.
Typical technical reasons include:
- Non-deterministic traversal of modules or chunk groups during optimization
- Differences in dependency installation layout after reinstalling packages
- A framework or bundler version where stable chunk hashing is not guaranteed in all scenarios
- Use of dynamic imports or shared modules that move between chunk boundaries during optimization
- Environment differences such as changing Node.js versions
For the linked reproduction, the issue is best understood as a deterministic build expectation colliding with a bundler output process that is not fully stable for chunk naming. The app code may be unchanged, but the build artifact graph is being serialized differently between runs.
Step-by-Step Solution
The fix is usually not a single code change inside a page component. Instead, you need to make the build environment and bundling behavior as deterministic as possible, then verify whether the issue comes from your app, your dependency tree, or the framework version.
1. Lock the runtime and dependency graph
First, make sure every build runs with the exact same toolchain.
node -v
npm -v
Commit and preserve your lockfile, then perform a clean install:
rm -rf node_modules .next
npm ci
npm run build
If you are currently using npm i, switch to npm ci in CI and reproducibility checks. That prevents subtle dependency tree drift.
2. Rebuild multiple times and compare output
Run the build repeatedly after cleaning the output directory each time:
rm -rf .next
npm run build
find .next -type f | sort > build1.txt
rm -rf .next
npm run build
find .next -type f | sort > build2.txt
diff build1.txt build2.txt
If filenames differ, inspect the build manifests directly:
diff .next/build-manifest.json /path/to/previous/build-manifest.json
This confirms whether the instability is in static assets, the manifest, or both.
3. Pin the exact Next.js and React versions
Chunk instability is often version-specific. Check your installed framework versions:
npm ls next react react-dom webpack
Then pin them explicitly in package.json instead of using broad ranges:
{
"dependencies": {
"next": "13.4.19",
"react": "18.2.0",
"react-dom": "18.2.0"
}
}
After pinning, reinstall cleanly:
rm -rf node_modules package-lock.json .next
npm install
npm run build
If the issue disappears after upgrading or downgrading Next.js, the root cause is likely inside the frameworkâs bundling integration rather than your app code.
4. Force deterministic webpack IDs where possible
If you need to experiment, add a custom next.config.js and inspect webpack behavior. In some cases, setting deterministic IDs helps reduce output drift.
/** @type {import('next').NextConfig} */
const nextConfig = {
webpack: (config, { dev, isServer }) => {
if (!dev) {
config.optimization = {
...config.optimization,
moduleIds: 'deterministic',
chunkIds: 'deterministic'
}
}
return config
}
}
module.exports = nextConfig
Important: Next.js already manages much of webpack internally, so this is a diagnostic step as much as a fix. If this setting reduces manifest churn, you have confirmed that the problem is related to ID stability during optimization.
5. Reduce unstable chunk boundaries
If your app uses many shared dynamic imports, test whether simplifying split points stabilizes the output. For example, replace or temporarily remove one dynamic import at a time:
import dynamic from 'next/dynamic'
const Chart = dynamic(() => import('../components/Chart'))
Then rebuild and compare outputs. If one split point causes repeated chunk reshuffling, isolate that dependency and inspect its transitive imports.
6. Verify there is no hidden environment input
Even when source files are unchanged, build-time environment variables can alter the graph. Check for any use of process.env, feature flags, or conditional imports.
grep -R "process.env" .
Also verify that CI and local builds use the same environment.
7. Use a reproducible comparison script
For teams diagnosing this in CI, add a script that runs two clean builds and compares manifests:
rm -rf .next
npm ci
npm run build
cp .next/build-manifest.json /tmp/build-manifest-1.json
rm -rf .next
npm run build
cp .next/build-manifest.json /tmp/build-manifest-2.json
diff /tmp/build-manifest-1.json /tmp/build-manifest-2.json
If the diff persists under a fixed environment, you have a strong reproduction for a framework-level determinism bug.
8. Practical resolution path
The most reliable fix path is usually:
- Use npm ci instead of
npm i - Pin Node.js and Next.js versions exactly
- Delete
node_modules,.next, and lockfile only when intentionally resetting - Test a newer or older Next.js patch release
- If needed, open or follow a framework issue with a minimal reproduction
When the application code is constant but output chunks change, the long-term fix usually lands in the bundler or framework version rather than in business logic files.
Common Edge Cases
- Different Node.js versions: Node 16, 18, and 20 can expose differences in package resolution behavior or runtime ordering.
- Lockfile drift: A teammate running
npm imay update transitive dependency versions without realizing it. - Dynamic imports with shared dependencies: A module shared by multiple async boundaries can move between chunks during optimization.
- Monorepo symlinks: Workspace linking can alter module identity and chunk splitting behavior.
- OS-specific differences: File system ordering can occasionally influence traversal if a tool does not normalize inputs properly.
- Build plugins: Custom webpack plugins or loaders may inject timestamps, random values, or unstable ordering.
- Environment-based branching: Conditional imports based on
process.envcan produce different chunk graphs even when source code is unchanged.
FAQ
Why do chunk filenames change if my source code did not?
Because chunk filenames are based on the final bundled graph, not just your raw source files. If module ordering, chunk grouping, or internal IDs change, the resulting hashes can change too.
Is this always caused by my application code?
No. In many cases the application is only exposing a framework or bundler determinism issue. If repeated clean builds in the same environment still differ, the problem is likely below the app layer.
What is the fastest way to make builds more stable?
Use npm ci, pin exact Node.js and Next.js versions, remove broad semver ranges, clean .next before comparisons, and test whether a newer framework patch fixes the instability.
If you are troubleshooting this issue in production deployment pipelines, treat it as a reproducibility problem: stabilize the toolchain first, compare manifests second, and only then tune webpack behavior. That process will tell you whether the real fix belongs in your configuration or in the underlying Next.js build system.