The Problem
I upgraded a client app to Next.js 16, ran next build, watched it go green, deployed, and the very first request returned a 500. The build logs were spotless. The runtime logs were not:
Error: Cannot find module './chunks/8721.js'
Require stack:
- /var/task/.next/server/app/page.js
at Module._resolveFilename (node:internal/modules/cjs/loader)
{ code: 'MODULE_NOT_FOUND' }
next dev worked perfectly. The build reported no errors. But next start (and the production deploy) threw MODULE_NOT_FOUND on the first render. If you upgraded to Next.js 16 and your build succeeds while next start 500s with a Cannot find module error, this is the same trap.
Why It Happens
Next.js 16 makes Turbopack the default bundler for both next dev and next build. That is the headline feature, and for most apps it just works. The problem is apps with a custom webpack(config) function in next.config.
Turbopack does not run your webpack(config). It is a different bundler with a different plugin model, so it skips that function entirely. It does not warn you, it does not error, it just ignores it. If your project relies on a custom loader (SVGR for importing SVGs as components, a raw-loader for shaders or templates, a markdown loader, a custom alias resolver), those transforms never ran during the build.
So the build finishes clean because Turbopack's own module graph is internally consistent. The server crashes at request time because your code imports a module that the missing loader was supposed to produce, and that module is not in the output. MODULE_NOT_FOUND is the symptom of a transform that silently never happened.
Dev hides it in some setups because of how modules resolve on the fly, which is why this so often gets discovered only after the first production deploy.
The Fix
First, confirm the diagnosis. Open next.config.ts (or .mjs) and look for a webpack(config) function. If it is there, run one command to prove Turbopack is the cause:
NEXT_DISABLE_TURBOPACK=1 next build && next start
If the 500 disappears, your webpack loader chain is the missing piece, and you have two ways forward.
If you do not actually need the custom webpack loader, the better fix is to port it to a Turbopack-native rule and stay on the fast path. Most common loaders have a Turbopack equivalent now. Here is SVGR:
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
turbopack: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
},
}
export default nextConfig
With the rule under turbopack.rules, the transform runs inside the Turbopack pipeline, so the build output actually contains the modules your imports point at.
If your loader chain has no Turbopack equivalent yet, opt the build out of Turbopack and run it on webpack. Use the --webpack flag so the intent is explicit in your scripts:
{
"scripts": {
"build": "next build --webpack",
"start": "next start"
}
}
On Vercel, set NEXT_DISABLE_TURBOPACK=1 under Settings, Environment Variables, scoped to Production only. That keeps dev and preview deploys on Turbopack for speed while the production build uses the webpack pipeline your loaders need. The full behaviour is documented in the Next.js 16 upgrade guide.
The real trap here is the silence. A webpack(config) that gets ignored produces no warning, so the only signal is a runtime crash that looks unrelated to your config. Whenever you move an app to 16 and it has any custom bundler setup, assume Turbopack skipped it until you have proven otherwise.
Verify the fix by running the production build and server locally before you redeploy:
next build && next start
# then hit the route that crashed
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/
A 200 instead of a 500 on the route that failed means the missing transform is now in the bundle and the import resolves.
The Lesson
Turbopack as the default builder is a real speed win, but it does not read your webpack config, and Next.js 16 will not tell you it skipped it. A green next build followed by a MODULE_NOT_FOUND at request time almost always means a custom loader never ran. Port the loader to a turbopack.rules entry, or build with --webpack until you can. Bumping the version without checking your bundler config is how this ships to production unnoticed.
If your Next.js 16 upgrade builds clean but 500s in production and you need it stable before the next deploy window, that is the kind of migration I sort out for clients. See my services. For a related Turbopack build issue, read Next.js 16 Turbopack build OOM on Vercel.
Need a 16 upgrade that ships without surprise 500s? Get in touch.