Repository navigation
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
Description
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:
withPayloadadds the tracing include. It already mergesoutputFileTracingExcludesfordrizzle-kit(packages/next/src/withPayload/withPayload.js); adding a matchingoutputFileTracingIncludesentry ('**/*': ['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.- Avoid
fileTypeFromFile()in core.packages/payload/src/uploads/getFileByPath.tsandcheckFileRestrictions.tscould read the first ~4 KB and usefileTypeFromBuffer()(which only needsstrtok3/core, statically imported). That also avoids the dependency on a hidden dynamic import for other bundlers/frameworks (TanStack Start adapter etc.). - Upstream (
sindresorhus/file-type): offer a statically importable Node entry forfromFile. 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
- Payload
4.0.0-canary.37, Next16.4.0,output: 'standalone'innext.config. next build(orpayload build).ls .next/standalone/node_modules/strtok3/lib→ noindex.js/FileTokenizer.js.- 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. - 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
Activity