Lector

Loading documents

Load URLs and files, handle failures, and serve PDF.js assets from your own app.

Root accepts a URL string, a URL, an ArrayBuffer, a typed array, or a PDF.js DocumentInitParameters object as source. It reloads when the source reference changes.

URLs and authenticated requests

For a public file, use source="/sample.pdf". Remote URLs must allow the browser's origin through CORS. Lector cannot bypass a server's CORS policy.

For authenticated requests, keep the source object stable between renders:

authenticated-viewer.tsx
"use client";
 
import { CanvasLayer, Page, Pages, Root } from "@anaralabs/lector";
import { useMemo } from "react";
import "./pdf-setup";
 
export default function AuthenticatedViewer({
  url,
  token,
}: {
  url: string;
  token: string;
}) {
  const source = useMemo(
    () => ({ url, httpHeaders: { Authorization: `Bearer ${token}` } }),
    [url, token],
  );
 
  return (
    <Root source={source} style={{ height: 600 }}>
      <Pages><Page><CanvasLayer /></Page></Pages>
    </Root>
  );
}

For cookie-based cross-origin requests, PDF.js accepts withCredentials: true. Your server must allow credentialed requests from the specific app origin. See the PDF.js loading parameters.

Do not create source={{ url }} or new Uint8Array(...) inline during render: a fresh object triggers another load. documentOptions is different: changes are read on the next document load and do not trigger one by themselves.

Local files

A File or Blob is not a direct source value. Create an object URL and release it when the file changes or the component unmounts:

local-file-viewer.tsx
"use client";
 
import { CanvasLayer, Page, Pages, Root, TextLayer } from "@anaralabs/lector";
import { useEffect, useState } from "react";
import "./pdf-setup";
 
export default function LocalFileViewer() {
  const [file, setFile] = useState<File | null>(null);
  const [url, setUrl] = useState<string | null>(null);
 
  useEffect(() => {
    if (!file) {
      setUrl(null);
      return;
    }
    const nextUrl = URL.createObjectURL(file);
    setUrl(nextUrl);
    return () => URL.revokeObjectURL(nextUrl);
  }, [file]);
 
  return (
    <>
      <label>
        Choose a PDF
        <input
          type="file"
          accept="application/pdf,.pdf"
          onChange={(event) => setFile(event.target.files?.[0] ?? null)}
        />
      </label>
      {url && (
        <Root key={url} source={url} style={{ height: 600 }}>
          <Pages><Page><CanvasLayer /><TextLayer /></Page></Pages>
        </Root>
      )}
    </>
  );
}

You can also read await file.arrayBuffer() and pass the bytes. PDF.js may transfer typed arrays to its worker, taking ownership of the buffer. Keep a separate copy if your app needs the original bytes or must retry a load. See PDF.js binary data loading.

Show errors and retry

loader is waiting content, not an error boundary. Without your own error UI, a failed load can leave it visible. onError receives { error, phase, source }; error is unknown. Render failures outside Root, since its children are not mounted during loading.

resilient-viewer.tsx
"use client";
 
import { CanvasLayer, Page, Pages, Root, TextLayer } from "@anaralabs/lector";
import { useState } from "react";
import "./pdf-setup";
 
export default function ResilientViewer({ source }: { source: string }) {
  return <DocumentSession key={source} source={source} />;
}
 
function DocumentSession({ source }: { source: string }) {
  const [attempt, setAttempt] = useState(0);
  const [failed, setFailed] = useState(false);
 
  if (failed) {
    return (
      <div role="alert">
        <p>We couldn't open this PDF. Check the file or try again.</p>
        <button type="button" onClick={() => {
          setFailed(false);
          setAttempt((value) => value + 1);
        }}>
          Try again
        </button>
      </div>
    );
  }
 
  return (
    <Root
      key={attempt}
      source={source}
      style={{ height: 600 }}
      loader={<p role="status">Loading PDF…</p>}
      onError={({ error, phase }) => {
        console.error(`PDF failed during ${phase}`, error);
        setFailed(true);
      }}
    >
      <Pages><Page><CanvasLayer /><TextLayer /></Page></Pages>
    </Root>
  );
}
PhaseWhat failed
pdfjs-loadImporting the PDF.js runtime
document-loadLoading or parsing the document, including worker initialization, network, or password errors
viewport-generationResolving pages after the document loaded

onDocumentLoad={({ proxy, source }) => …} fires when PDF.js resolves the document, before Lector finishes generating page viewports. It is not a signal that canvases have painted. onError covers initialization failures; it does not catch every later canvas, text, or annotation rendering error.

For a known PDF password, pass documentOptions={{ password }}. Lector does not expose a password-prompt callback. If credentials or the password change, reload using a new source reference or remount Root with a new key.

Self-host PDF.js assets

Lector supplies versioned jsDelivr URLs for auxiliary PDF.js resources by default. For offline or same-origin deployment, copy the resources from your installed pdfjs-dist package:

mkdir -p public/pdfjs
cp node_modules/pdfjs-dist/legacy/build/pdf.worker.min.mjs public/pdfjs/
cp -R node_modules/pdfjs-dist/wasm public/pdfjs/
cp -R node_modules/pdfjs-dist/cmaps public/pdfjs/
cp -R node_modules/pdfjs-dist/standard_fonts public/pdfjs/
cp -R node_modules/pdfjs-dist/iccs public/pdfjs/

Keep these assets synchronized with the dependency when building or deploying. Configure the worker as in installation, then pass the auxiliary directories to Root:

<Root
  source="/sample.pdf"
  documentOptions={{
    wasmUrl: "/pdfjs/wasm/",
    cMapUrl: "/pdfjs/cmaps/",
    cMapPacked: true,
    standardFontDataUrl: "/pdfjs/standard_fonts/",
    iccUrl: "/pdfjs/iccs/",
  }}
  style={{ height: 600 }}
>
  <Pages><Page><CanvasLayer /><TextLayer /></Page></Pages>
</Root>

Include trailing slashes and any application path prefix. Image decoders matter for scanned PDFs; character maps and font data matter for documents with non-embedded or CJK fonts. Serve .mjs as JavaScript and .wasm as application/wasm, and check your deployment's worker and resource CSP rules if the browser blocks requests.

documentOptions overrides both source-object options and Lector's defaults. Supplying your own CanvasFactory also replaces Lector's dark-mode scratch-canvas factory.

On this page