Skip to content

Build1 publisher3 min readPublished

Turbopack turns require.resolve into an integer module ID in Next.js 16 production builds

Turbopack, Next.js 16's default bundler, swapped a require.resolve call for an integer module ID at build time and broke a PDF tool that passed every test. Type checks, unit tests and direct calls never run the bundled output, so none of them could see the swap.

The Engineer · Build desk

Illustration accompanying Turbopack turns require.resolve into an integer module ID in Next.js 16 production builds

What happened

  • The integer went into path.dirname, which threw a TypeError, and the tool's responses in the verification environment came back with no image.
  • According to the Next.js docs, next build in version 16 uses Turbopack by default and switches to webpack only when --webpack is passed.
  • The author confirmed the behavior on 2026-09-25 with Next.js 16.3.6 and 16.2.2 running on Node 25.7.0.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • decision Teams whose Next.js 16 server code reads package files at runtime need a pre-deploy step that boots the production build and calls it from outside, because that step alone runs the bundled code.
  • constraint The working-directory fix ties this code's correctness to Turbopack, so adding --webpack to next build would break it again.
  • contradiction Anyone who acted on the original incident record would have guarded import.meta.url, a value the reproduction shows was never altered.

The reproduction fits in one function [18]. It builds two require functions with createRequire and asks each one to resolve next/package.json [18]. The first uses import.meta.url, the module's own URL, as its base. The second uses a made-up index.js under process.cwd() [18]. Both results go to path.dirname.

In source, require.resolve returns the location of a file inside a package [20]. In the Turbopack build, that call is resolved at build time, and the output holds an integer module ID where the path used to be [6]. path.dirname receives the integer and throws a TypeError [7]. The route handler never gets a path.

None of the pre-deploy checks could see this. The type check, the unit tests and a direct call from a script all run the source, and none of them passes through the build step [8]. The Next.js Turbopack API reference also states that type checking is not part of Turbopack's processing [17]. The agent had even inspected the generated JPEG by eye [3], on a code path that production does not execute.

The fix depends on the bundler. The team changed the base for resolving the package location to the process working directory [9]. The author reports that this works with Turbopack and does not work with webpack [10]. Next.js 16 falls back to webpack only when --webpack is passed to next build [16], so the fix holds as long as nobody adds that flag. The post offers a turbopackIgnore annotation as the second option and compares the conditions under which each one works [19]. In my view the working-directory fix is acceptable in a service whose build command is pinned in one place. I'd put a comment next to the resolve call naming the bundler it depends on.

The diagnosis written at the time was wrong in one place. The record said import.meta.url had been replaced with a number [11]. The author's minimal reproduction disagrees: import.meta.url stayed a string, and the value that became an integer was resolve's return value [12]. Moving the base off import.meta.url is the remedy either diagnosis would prescribe. A passing fix could not tell the two apart, and the error only surfaced when the author built the smallest failing case.

After the failure, the team wrote down a lesson: start the same build as production and call it from outside [13]. Ten days later the same procedure went into the service's operations document [14]. Of the checks the agent had run before deploying, none executed the bundler's output [8]. The new one does.

The author confirmed the behavior on 2026-09-25 with Next.js 16.3.6 and 16.2.2 on Node 25.7.0 [15]. The write-up targets Next.js server code that reads files inside a package at runtime [21]. For the result to carry over to another codebase, that code has to resolve a package location at runtime, the way the reproduction's import.meta.url branch does [18].

What to watch

  • Whether a later Next.js 16.x release changes or documents Turbopack's build-time replacement of require.resolve with module IDs.
  • Whether the turbopackIgnore annotation, the author's second option, holds under both Turbopack and webpack builds.
  • Whether other Next.js 16 users report the same integer swap in packages that read their own files at runtime.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories