dockerpdfnext.jspdfjs-dist

pdfjs-dist + Next.js App Router - pdf.worker.mjs fails to load after build/deploy (MIME type "text/html" error)


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)

Solution

  • 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.