Skip to content

v4: file-type 22 loads strtok3 via import(variable) → strtok3/lib/index.js missing from Next standalone output, fileTypeFromFile throws ERR_MODULE_NOT_FOUND #18588

Open

Description

@CommerceMax

Describe the Bug

With Payload 4.0.0-canary.37 on Next.js 16 with output: 'standalone', any code path that ends in fileTypeFromFile() throws at runtime:

Error: Cannot find module '/app/node_modules/strtok3/lib/index.js' imported from /app/node_modules/file-type/source/index.js

In our deployment this surfaces as a 500 from the admin (e.g. while working with an upload field and the media drawer) and in the server log as the error above. Uploads that go through fileTypeFromBuffer are not affected, which makes it look intermittent.

Root cause

Payload v4 bumped file-type from 21.3.4 to 22.0.1. In 22, FileTypeParser.fromFile() loads strtok3's Node entry through a deliberately opaque wrapper so bundlers don't see it:

// file-type/source/index.js
function importAtRuntime(specifier) {
	return import(specifier);
}
// …
async fromFile(path) {
	const [{default: fsPromises}, {FileTokenizer}] = await Promise.all([
		importAtRuntime('node:fs/promises'),
		importAtRuntime('strtok3'),
	]);

The static import in the same file is import * as strtok3 from 'strtok3/core'. strtok3's export map is:

{ ".": { "node": "./lib/index.js", "default": "./lib/core.js" }, "./core": "./lib/core.js" }

Next.js' output file tracing only sees strtok3/core → lib/core.js. It can't follow import(specifier), so lib/index.js (and lib/FileTokenizer.js, lib/types.js) are never copied into .next/standalone/node_modules/strtok3/lib. At runtime Node resolves strtok3 via the "node" condition → lib/index.js → missing.

Contents of .next/standalone/node_modules/strtok3/lib after next build:

AbstractTokenizer.js  BlobTokenizer.js  BufferTokenizer.js  ReadStreamTokenizer.js  core.js

(no index.js, no FileTokenizer.js). With v3 (file-type@21.3.4) this didn't happen, because 21 resolved its Node entry through its own "node" export condition with static imports.

Workaround (verified)

In the app's next.config:

outputFileTracingIncludes: {
  '/**': ['./node_modules/strtok3/lib/**/*.js'],
},

After that lib/index.js, FileTokenizer.js and types.js are in the standalone output and fileTypeFromFile() works again (checked locally inside .next/standalone and in a deployed container: uploads return 201, the drawer no longer gets a 500).

Suggested fix (not opening a PR, just suggesting)

Any one of these would fix it for everyone using withPayload + standalone:

  1. withPayload adds the tracing include. It already merges outputFileTracingExcludes for drizzle-kit (packages/next/src/withPayload/withPayload.js); adding a matching outputFileTracingIncludes entry ('**/*': ['node_modules/strtok3/lib/**/*.js'], resolved relative to the project/workspace root) keeps the fix next to the existing tracing tweaks. Simplest and framework-local.
  2. Avoid fileTypeFromFile() in core. packages/payload/src/uploads/getFileByPath.ts and checkFileRestrictions.ts could read the first ~4 KB and use fileTypeFromBuffer() (which only needs strtok3/core, statically imported). That also avoids the dependency on a hidden dynamic import for other bundlers/frameworks (TanStack Start adapter etc.).
  3. Upstream (sindresorhus/file-type): offer a statically importable Node entry for fromFile. Less in Payload's control, so 1 or 2 seem better.

Link to the code that reproduces this issue

No public repo (private project). Minimal reproduction: any Payload v4 canary app with output: 'standalone' (steps below).

Reproduction Steps

  1. Payload 4.0.0-canary.37, Next 16.4.0, output: 'standalone' in next.config.
  2. next build (or payload build).
  3. ls .next/standalone/node_modules/strtok3/lib → no index.js / FileTokenizer.js.
  4. In .next/standalone, run:
    import { fileTypeFromFile } from 'file-type'
    console.log(await fileTypeFromFile('some.png'))
    → ERR_MODULE_NOT_FOUND … strtok3/lib/index.js imported from …/file-type/source/index.js.
  5. In a deployed app, any Payload path that calls fileTypeFromFile (getFileByPath, checkFileRestrictions) fails the same way.

Which area(s) are affected?

area: core, area: uploads, plugin: next (withPayload)

Environment Info

Binaries:
  Node: 24.21.0
  npm: 11.19.0
Relevant Packages:
  payload: 4.0.0-canary.37
  next: 16.4.0
  @payloadcms/db-postgres: 4.0.0-canary.37
  @payloadcms/drizzle: 4.0.0-canary.37
  @payloadcms/next/utilities: 4.0.0-canary.37
  @payloadcms/richtext-lexical: 4.0.0-canary.37
  @payloadcms/ui/shared: 4.0.0-canary.37
  file-type: 22.0.1
  strtok3: 10.3.5
  react: 19.3.0
  react-dom: 19.3.0
Deployed: node:24-alpine container, Next standalone output, Turbopack build
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions