Handling non-Latin fonts, standard fonts, and JPEG 2000 in react-pdf
Table of contents
react-pdf for special PDF requirements — CMap files for Chinese, Japanese, and Korean text; standard font data for legacy PDFs; JPEG 2000 WebAssembly decoding; and custom HTTP headers for authenticated endpoints.
- Pass extra PDF.js resources through the
optionsprop on<Document>—cMapUrl(CJK text),standardFontDataUrl(non-embedded fonts),wasmUrl(JPEG 2000 images), andhttpHeaders/withCredentials(authentication). - Self-host the files in production. Content delivery network (CDN) URLs are fine for prototyping but introduce CSP and supply-chain risk.
- Memoize the
optionsobject —<Document>compares it with===, so a fresh object on every render kicks off a reload. wasmUrlis a PDF.js v5+ option; older versions fall back to the bundled JavaScript decoder.
Some PDFs require additional resources to render correctly: character maps for CJK text, standard font data for older PDFs, and WebAssembly (WASM) decoders for JPEG 2000 images. react-pdf passes these through to PDF.js via the options prop.
CMap support (non-Latin characters)
PDFs with Chinese, Japanese, Korean, or other non-Latin characters need Character Map (CMap) files for correct text rendering.
Option A: Copy CMaps to public directory
Copy the CMap files from pdfjs-dist into your build output:
cp -r node_modules/pdfjs-dist/cmaps public/cmapsThen configure:
const options = { cMapUrl: "/cmaps/", cMapPacked: true,};
<Document file={file} options={options}> <Page pageNumber={1} /></Document>cMapPacked defaults to true in current PDF.js versions, but older versions defaulted to false. Setting it explicitly avoids surprises across upgrades.
Option B: Use CDN
Skip the copy step and load the CMap files directly from a CDN instead:
import { pdfjs } from "react-pdf";
const options = { cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjs.version}/cmaps/`, cMapPacked: true,};CDN URLs (unpkg.com, jsdelivr.net) are convenient for prototyping but risky in production — they introduce supply-chain attack surface, can be blocked by strict CSP policies, and add a third-party dependency to your uptime. Self-host the files from node_modules/pdfjs-dist/ for anything user-facing.
Build tool integration
These bundlers support copy plugins:
Vite:
import { viteStaticCopy } from "vite-plugin-static-copy";
export default { plugins: [ viteStaticCopy({ targets: [ { src: "node_modules/pdfjs-dist/cmaps/*", dest: "cmaps", }, ], }), ],};webpack (CopyPlugin):
const CopyPlugin = require("copy-webpack-plugin");
module.exports = { plugins: [ new CopyPlugin({ patterns: [ { from: "node_modules/pdfjs-dist/cmaps", to: "cmaps" }, ], }), ],};Standard fonts
Older or non-PDF/A PDFs may reference the 14 “standard fonts” (Helvetica, Times-Roman, Courier, etc.) by name instead of embedding them. PDF 2.0 (2017) requires fonts to be embedded, and PDF/A has required it since 2005 — but plenty of in-the-wild files still skip embedding. Without standardFontDataUrl, PDF.js can’t substitute and the text renders incorrectly.
Setup
Copy the standard fonts directory:
cp -r node_modules/pdfjs-dist/standard_fonts public/standard_fontsConfigure:
const options = { standardFontDataUrl: "/standard_fonts/",};
<Document file={file} options={options} />Or use CDN:
const options = { standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjs.version}/standard_fonts/`,};JPEG 2000 support (WASM)
Some PDFs use JPEG 2000 compression for images. PDF.js can decode these using a WebAssembly module.
wasmUrl is a PDF.js v5+ option. On v4 and earlier, JPEG 2000 falls back to the bundled JavaScript decoder — the option is silently ignored, so don’t expect a speedup unless you’ve upgraded.
Setup
Copy the WASM files:
cp -r node_modules/pdfjs-dist/wasm public/wasmConfigure:
const options = { wasmUrl: "/wasm/",};
<Document file={file} options={options} />Combined configuration
A production setup typically includes all three:
import { pdfjs } from "react-pdf";
// Define outside component to avoid re-creation.const options = { cMapUrl: `https://unpkg.com/pdfjs-dist@${pdfjs.version}/cmaps/`, standardFontDataUrl: `https://unpkg.com/pdfjs-dist@${pdfjs.version}/standard_fonts/`, wasmUrl: "/wasm/",};
function PDFViewer({ file }) { return ( <Document file={file} options={options}> <Page pageNumber={1} /> </Document> );}Remember: The options object must be defined outside the component or memoized with useMemo. It uses === equality like the file prop.
Custom HTTP headers
For authenticated PDF endpoints, pass headers via options:
const options = useMemo( () => ({ httpHeaders: { Authorization: `Bearer ${token}`, }, withCredentials: true, }), [token],);
<Document file={{ url: protectedUrl }} options={options} />When do you need these?
| Resource | When needed | Symptom without it |
|---|---|---|
| CMaps | PDF has CJK text | Characters render as blank/garbled |
| Standard fonts | PDF doesn’t embed all fonts | Text appears in wrong font or missing |
| WASM | PDF has JPEG 2000 images | Images don’t render (PDF.js v5+ only) |
| HTTP headers | Authenticated endpoints | 401/403 errors |
Most modern PDFs embed everything they need. You can start without these and add them when you encounter rendering issues.
Key points
- All special resource paths go through the
optionsprop onDocument. - CDN paths are the easiest setup — no build tool configuration needed.
- For production, self-host the files for reliability and CSP compliance.
- Memoize the
optionsobject to avoid unnecessary rerenders. httpHeadersandwithCredentialsalso go inoptions.- Most PDFs work without any of these — add them when you see rendering issues.
How Nutrient Web SDK handles this
All the CMap configuration, standard font paths, WASM decoder setup, and build tool plugins shown above are unnecessary with Nutrient Web SDK:
// No CMap files, no standard fonts, no WASM decoder, no HTTP headers config.const instance = await NutrientViewer.load({ container: "#pdf-container", document: "document.pdf", // CJK fonts, standard fonts, JPEG 2000 — all handled internally.});You don’t need cMapUrl, standardFontDataUrl, or wasmUrl in options, and no Vite/webpack copy plugins are required. Nutrient bundles all font rendering, CMap support, and image decoders internally. CJK text, legacy PDFs, and JPEG 2000 images render correctly out of the box — in any framework, with zero configuration.
Learn more about Nutrient Web SDK | Migration guide | Contact Sales
FAQ
PDF.js needs CMap files to map CJK character codes to glyphs. Set cMapUrl in the options prop — either pointing at a self-hosted /cmaps/ directory or, for prototyping, a CDN URL. Also set cMapPacked: true for binary .bcmap files (the default in current PDF.js).
standardFontDataUrl for every PDF?No — it’s only needed for PDFs that don’t embed their fonts. PDF/A and PDF 2.0 require embedding, so compliant files don’t need it. Older or non-compliant files that reference the 14 base fonts (Helvetica, Times-Roman, Courier, etc.) by name do.
wasmUrl improve performance?It only improves performance on PDF.js v5 or later, and only for PDFs containing JPEG 2000 images. On older versions, the option is silently ignored and decoding falls back to the bundled JavaScript decoder. If you’ve set wasmUrl and see no difference, check your PDF.js version first.
No. CDN URLs add a third-party dependency, expand your supply-chain attack surface, and may be blocked by strict CSP policies. Use them for prototyping or internal tools. For production, copy the files out of node_modules/pdfjs-dist/ at build time with vite-plugin-static-copy or copy-webpack-plugin.
The options prop is compared by reference (===). If you inline it as <Document options={{ cMapUrl: ... }}>, every render produces a fresh object and react-pdf reloads the document. Hoist options outside the component, or wrap it in useMemo.
Wrap the options object in useMemo keyed on the token, as shown in the custom HTTP headers section. When the token rotates, the memo recomputes, react-pdf sees a new options reference, and react-pdf refetches with the updated Authorization header.