fix(web): add CSS stub loader for Payload CMS route collection in Docker
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

Node ESM can't handle .css imports during Next.js route collection.
This loader intercepts .css resolutions and returns empty modules,
fixing the build for all Payload deps (richtext-lexical, react-image-crop, etc.)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-09 00:35:04 +01:00
parent 83ba1aa373
commit 3f46a6657a
3 changed files with 36 additions and 3 deletions

View File

@@ -25,9 +25,9 @@ ENV NEXT_PUBLIC_URL=$NEXT_PUBLIC_URL
ENV NEXT_PUBLIC_PRODUCT_NAME=$NEXT_PUBLIC_PRODUCT_NAME
ENV NEXT_PUBLIC_DEFAULT_LOCALE=$NEXT_PUBLIC_DEFAULT_LOCALE
# TURBOPACK=0 forces webpack for production build — Payload CMS's
# richtext-lexical CSS imports fail under Turbopack.
ENV TURBOPACK=0
# Node ESM loader that stubs .css imports during route collection.
# Payload CMS deps import .css files that Node can't handle outside webpack.
ENV NODE_OPTIONS="--import ./apps/web/css-stub-loader.mjs"
RUN npx turbo run build --filter=web...
# Stage 2: runtime — standalone output only

View File

@@ -0,0 +1,31 @@
/**
* Node.js ESM custom loader — stubs .css imports as empty modules.
*
* Next.js 16 does route collection in raw Node ESM (not webpack/turbopack).
* Payload CMS dependencies import .css files which Node can't handle.
* This loader intercepts .css resolutions and returns an empty module.
*
* Usage: NODE_OPTIONS="--import ./apps/web/css-stub-loader.mjs"
*/
import { register } from "node:module";
register(
"data:text/javascript," +
encodeURIComponent(`
export function resolve(specifier, context, nextResolve) {
if (specifier.endsWith('.css')) {
return { url: 'data:text/javascript,export default {};', shortCircuit: true };
}
return nextResolve(specifier, context);
}
export function load(url, context, nextLoad) {
if (url.endsWith('.css')) {
return { format: 'module', source: 'export default {};', shortCircuit: true };
}
return nextLoad(url, context);
}
`),
import.meta.url,
);

View File

@@ -89,6 +89,8 @@ const config: NextConfig = {
"@payloadcms/db-sqlite",
"@payloadcms/richtext-lexical",
"@payloadcms/next",
"@payloadcms/ui",
"react-image-crop",
"sharp",
],
turbopack: {