What is paged.js?
paged.js is a javascript library to transform HTML document into print-ready pdf.
paged.js comes as two different scripts - paged.js and paged.polyfill.js. There is also a commend line version which can be used to generate a PDF using a headless browser.
The differences between paged.js and paged.polyfill.js:
| File | What it does | How it works | When to use it |
|---|---|---|---|
paged.polyfill.js |
Acts like a print-layout engine for the whole webpage | Searches the entire document, moves the content, paginates it, and replaces your DOM with paged output | When you’re converting your whole page to paginated output (e.g., print / PDF export) |
paged.js (with import { Previewer } from "pagedjs") |
You control what to paginate and where to output | You give it a specific DOM node as input, and another node as output, and it inserts the paginated pages there | When you want to paginate only part of the page, or use Paged.js inside React / Vue / Svelte |
What actually happens in practice
paged.polyfill.js
- Automatically waits for the document to load
- Finds everything in
<body> - Moves it into a hidden template
- Generates page boxes + flows
- Rewrites the document with pages in place
Think of it like printing your webpage in the browser, but beautifully.
Pros:
- Headers/footers (
position: running()) work automatically - No manual JavaScript setup
- Good for “convert my whole HTML doc to print layout”
Cons:
- Hard to use in React (because it rewrites the DOM)
- You can’t choose which div to paginate
- Hard to preview in one place and edit in another
paged.js (with the Preview API)
You use:
1 | import { Previewer } from "pagedjs"; |
This lets you:
- Keep your HTML exactly where it is
- Paginate only specific content
- Render the pages into a separate container
Pros:
- Perfect for React / SPA / dynamic content
- Source stays editable
- Output is controlled and isolated
Cons:
- Running headers/footers do not auto-work unless:
- You pass CSS explicitly
- The running header element is inside the paginated source
In plain language
paged.polyfill.js
“I’ll print your entire document as a book. Stand back.”
paged.js + Previewer()
“Tell me which content to convert, and where to output the pages.”
Caveats
1. A server is required
If you open your html file with paged.js embed in the file:// protocal, the page will be blank. There are error messages on the console panel, sush as
1 | Access to XMLHttpRequest at 'file://<path-to-your-file>/example.css' from origin 'null' has been blocked by CORS policy |
The reason for the error is that paged.js has to work with a web server so that it can fetch CSS file content used in your html file by using XMLHttpRequest API and apply it to the generated pages.
2. The preview does not work correctly when the source element is hidden
When we use paged.js with a hidden source element (e.g. display:none;), only some of the pages will be generated on the target container. Besides, all the page content will be hidden except on the first page.
You may want to hide the source container with a collapse height:
1 | .collapsed { |
Other ways like display: none;, opacity: 0 or position: aboslute: do not work.
3. Headers/Footers are not added to each page
If you are using paged.js, make sure the headers/footer are inside the source element, otherwise they won’t be rendered in pages.
What’s more, remember to check if the CSS file are included when the preview() function is called.
1 | import { Previewer } from "pagedjs"; |
Page number is always 2 on all the pages
The can happen when the CSS files are included in the head tag and then included again with the preview() function is called.
Consider removing duplicated CSS file from the head tag and only load them with the preview() function call.
Paged.js preview error: Error: item doesn’t belong to list
This error can occur when the Previewer object is reused to perform preivew multiple times. This is a bug reported in 2024 and hasn’t been fixed.
We need to create a new Previewer object every time when we need to call the preview() function to work around this issue.