I'm using pdfjs-dist
(version 4.10.38
) in a Next.js 15 App Router
project (next@15.2.4
) to render and interact with PDFs. Everything works locally during development, but once I build and deploy, the PDF worker fails to load.
In the browser console, I get the following errors:
Failed to load module script: The server responded
with a non-JavaScript MIME type of "text/html".
Strict MIME type checking is enforced for module scripts per HTML spec.
Error loading PDF: Error: Setting up fake worker failed:
"Failed to fetch dynamically imported module:
http://localhost:3000/pdf.worker.mjs"
This is how I set the worker path in my React hook:
import { GlobalWorkerOptions, getDocument } from "pdfjs-dist";
useEffect(() => {
GlobalWorkerOptions.workerSrc = "/pdf.worker.mjs";
const loadingTask = getDocument({ url: pdfUrl });
// ...
}, [pdfUrl]);
I placed the pdf.worker.mjs
file inside the /public folder. During dev (yarn dev
), it loads fine. But after running yarn build && yarn start
or deploying the app using Docker, it fails with the MIME type error above.
My Dockerfile (simplified):
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
RUN yarn build
FROM node:22-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
# THIS WAS THE PROBLEMATIC LINE:
# COPY ./public /app/.next/public
# I thought this was enough to expose static assets
EXPOSE 3000
CMD ["yarn", "start"]
Versions Tested With:
"pdfjs-dist": "4.10.38"
"next": "^15.2.4" (App Router)
tl;dr: Change the Dockerfile to copy public
into the actual root-level /public
folder:
COPY --from=builder /app/public ./public
This error happens because Next.js expects all static assets to be located in the /public
directory at the root of your project — not inside .next/public
.
When you copy the public
folder into .next/public
, like this:
COPY ./public /app/.next/public
…it does not make those files available via /yourfile
routes. Instead, they’re just dumped into a subfolder inside .next
that isn’t served by Next.js' static file handling.
So when the browser requests /pdf.worker.mjs
, it doesn’t find it, and Next.js returns its default HTML fallback (404 page) — with MIME type text/html
. Since the browser expected a JS module, it throws:
Failed to load module script: The server responded with a non-JavaScript MIME type of "text/html".
The fix: Change the Dockerfile to copy public
into the actual root-level /public
folder:
COPY --from=builder /app/public ./public
This ensures that files like /public/pdf.worker.mjs
will be served as:
http://yourdomain.com/pdf.worker.mjs
with the correct application/javascript
MIME type.