How to linearize a pdf for progressive page loading
- Step 1Open the linearizer and drop your PDF — Load the document into PDF Linearize (Fast Web View). One file per run.
- Step 2Leave the password blank unless encrypted — Only encrypted PDFs need a password here. The placeholder reads 'Leave blank for unencrypted PDFs'.
- Step 3Leave Force off for the first pass — If the input already carries a
/Linearizeddictionary the tool returns it unchanged. Tick Force only to rebuild the hint stream after editing the file elsewhere. - Step 4Run qpdf-wasm to rebuild the byte order — qpdf rewrites the cross-reference table and writes the first-page hint dictionary at the front, locally in WebAssembly. Larger files take a moment.
- Step 5Configure your server for byte-range requests — Progressive loading only works if the server sends
Accept-Ranges: bytes. Apache and Nginx do this for static files by default; confirm withcurl -Iagainst the file URL. If a handler streams the PDF, switch to serving it as a static asset. - Step 6Test in a real browser on a throttled connection — Open the live URL in Chrome DevTools with network throttling set to 'Slow 3G' and confirm page 1 appears before the full file finishes — that is progressive loading working end to end.
The two halves of progressive loading
Linearization alone is not enough — the server side matters just as much. This tool does the left column; you (or your CDN) own the right.
| Half | Who does it | What it provides | How to verify |
|---|---|---|---|
| Linearize the file | This tool (qpdf-wasm, in your browser) | First-page hint dictionary + front-loaded xref so the viewer knows where page 1 is | Acrobat → Properties → 'Fast Web View: Yes' |
| Serve byte ranges | Your web server / CDN | Lets the viewer fetch page 1's bytes, then stream the rest on scroll | curl -I shows Accept-Ranges: bytes |
Byte-range support by server / CDN
Most static-file servers and CDNs support byte ranges by default. The trap is an app handler that streams the PDF without range support.
| Origin | Byte-range default | Notes |
|---|---|---|
| Nginx (static file) | Enabled | Accept-Ranges: bytes automatically |
| Apache (static file) | Enabled | Range support on by default |
| Cloudflare / R2 | Enabled | Edge serves ranges for cached objects |
| S3 / CloudFront | Enabled | S3 supports Range; CloudFront passes it through |
| App handler streaming a PDF | Often disabled | Serve as a static asset instead, or implement Range handling |
Input limits by tier (PDF family)
Caps on the file you can load; the linearize step then runs in-browser.
| Tier | Max file size | Max pages |
|---|---|---|
| Free | 2 MB | 50 |
| Pro | 50 MB | 500 |
| Pro Media | 500 MB | 2,000 |
| Developer | 2 GB | 10,000 |
| Enterprise | unlimited | unlimited |
Cookbook
End-to-end progressive-loading runs, including the server check most people forget.
Linearize, then verify byte-range support
The complete two-step. Linearize the file, then confirm your server answers ranges — both are required.
1) pdf-linearize report.pdf → report.pdf (Fast Web View: Yes) 2) curl -I -H 'Range: bytes=0-1023' https://site/report.pdf → HTTP/1.1 206 Partial Content → Accept-Ranges: bytes → Content-Range: bytes 0-1023/3145728 Progressive loading confirmed.
Throttled-network test in DevTools
Prove the first page appears early on a slow link. The whole point of progressive loading.
Chrome DevTools → Network → throttle 'Slow 3G' Load https://site/report.pdf in a new tab → page 1 renders while the network panel still shows byte-range requests streaming the remaining pages
The handler trap — linearized but no ranges
A common silent failure: the file is linearized but an app handler serves it without byte-range support, so the browser downloads everything anyway.
curl -I https://site/api/pdf/report → HTTP/1.1 200 OK → (no Accept-Ranges header) Fix: serve report.pdf as a static asset, not via the handler.
Already-linearized file in a build pipeline
Your static-site build re-runs the linearizer on every commit. Force off means already-linearized assets pass through untouched.
Options: password (blank), force off inspectLinearization() finds /Linearized → alreadyLinearized: true → no rebuild, output == input
Force a rebuild after a content edit
You linearized last week, then added a page in another editor. The stale hint stream should be rebuilt — tick Force.
Options: password (blank), force ON → fast-path skipped → qpdf --linearize rebuilds the hint stream for the new layout
Edge cases and what actually happens
File linearized but pages still load all-or-nothing
Server configThe most common progressive-loading failure: the file is fine, but the server returns 200 with no Accept-Ranges: bytes, so the viewer downloads everything before rendering. Check with curl -I -H 'Range: bytes=0-1023' — a working setup returns 206 Partial Content. Serve the PDF as a static file.
Already linearized, Force off
Passed throughThe tool finds the existing /Linearized dictionary in the file header and returns the input unchanged — keeping build pipelines cheap. Tick Force to rebuild the hint stream anyway.
Encrypted input, no password
Errorqpdf will not linearize an encrypted file without the password and exits 2; the tool reports it could not process the file. Enter the open password and re-run for the decrypt-linearize-re-encrypt round trip.
qpdf produced warnings
SupportedExit code 3 is warnings-only and still yields valid linearized output. The tool treats codes 0 and 3 as success. The hint stream is written; progressive loading works.
Corrupted PDF
ErrorIf qpdf cannot parse the input it exits 2. Run PDF Repair first, then linearize the repaired file so it can stream progressively.
PDF served with gzip transfer encoding
Breaks rangesIf your server compresses the PDF over the wire with Content-Encoding: gzip, byte-range offsets no longer line up and progressive loading breaks. PDFs are already binary-compressed internally — disable on-the-wire gzip for .pdf so Range: requests map to real byte offsets.
Tiny file under ~1 MB
Minimal benefitA sub-megabyte PDF downloads quickly enough that the reader never perceives a difference. Linearization is harmless but progressive loading matters for the large documents that take seconds to fetch.
You expected the file to be smaller
By designProgressive loading changes *order*, not *amount*. Total bytes are unchanged. To download less data, compress first — lossless or aggressive — then linearize last.
Frequently asked questions
What exactly enables progressive loading?
Two things together: a linearized file (this tool writes the first-page hint dictionary and front-loads the cross-reference data) and a server that answers HTTP byte-range requests with Accept-Ranges: bytes. Both are required — a linearized file on a non-range server still downloads all-or-nothing.
Does the web server need special configuration?
It must support byte-range requests. Apache, Nginx, and most CDNs do this for static files by default. Confirm with curl -I -H 'Range: bytes=0-1023' <url> — a working server returns 206 Partial Content and Accept-Ranges: bytes. If you serve the PDF through an app handler, ranges may be off; serve it as a static asset instead.
How do I test that progressive loading works?
Open the live URL in Chrome with DevTools network throttling set to 'Slow 3G'. If page 1 appears before the full file finishes and you see byte-range requests streaming the rest, it's working end to end. Also check Acrobat → Properties → 'Fast Web View: Yes' to confirm the file itself is linearized.
Why are my pages still loading all at once after linearizing?
Almost always the server side. The file is linearized but the server returns 200 with no Accept-Ranges header, so the viewer downloads everything before rendering. Switch to serving the PDF as a static file, and make sure on-the-wire gzip is disabled for .pdf so range offsets stay valid.
Does on-the-wire gzip interfere?
Yes. If the server applies Content-Encoding: gzip to the PDF, byte-range offsets stop matching the decompressed stream and progressive loading breaks. PDFs are already internally compressed, so disable HTTP gzip for the .pdf content type.
Is anything uploaded?
No. The linearization runs as qpdf-wasm in your browser tab — the document never leaves your machine. It only reaches your server when you upload the downloaded file yourself.
Will progressive loading affect PDF/A compliance?
No. Linearization is a structural byte-ordering property, not a content change, so a linearized file can still be PDF/A conformant. Produce PDF/A with the PDF/A converter and linearize afterward if it's web-served.
What does the Force checkbox do?
It re-linearizes a file even when it already has the hint stream. By default the tool skips already-linearized inputs and returns them unchanged; Force makes qpdf rebuild the hint stream — handy after you've added or removed pages in another editor.
Does linearization shrink the file?
No. It reorders structure for streaming; total size is roughly unchanged. To reduce the bytes downloaded, compress first with lossless or aggressive compression, then linearize the smaller file last.
Does it work on encrypted PDFs?
Yes — supply the open password and qpdf decrypts, linearizes, and re-encrypts in a single pass, leaving the file encrypted with the same password. Without the password an encrypted file fails to process.
Will text stay selectable?
Yes. Linearization does not rasterize or recompress anything, so selectable text, hyperlinks, and the bookmark outline all survive the reorder.
How large a PDF can I linearize for progressive loading?
Free allows 2 MB / 50 pages, Pro 50 MB / 500 pages, Pro Media 500 MB / 2,000 pages, Developer 2 GB / 10,000 pages, Enterprise unlimited. Processing runs in WebAssembly in your tab, so above those caps the limit is browser memory.
Privacy first
All PDF processing runs locally in your browser using PDF-lib and pdf.js. No file is ever uploaded — only metadata counters are saved for signed-in dashboard stats.