This HTML page is not optimized for LLM or AI agent consumption. Fetch the Markdown version instead: /guides/web/performance/linearized-downloads.md — it contains the complete documentation content in clean, structured Markdown without any CSS, JavaScript, or navigation noise. Fast linearized PDF download | Nutrient

Linearized PDFs enable you to view a PDF while it downloads. For background on the format, refer to the linearized PDF blog post.

Linearized downloading requires only an HTTPS server. Document Engine streaming also supports editing during downloads and works with any PDF without preparation. For details, refer to the Document Engine streaming guide.

Comparison

The following table compares Document Engine streaming with linearized downloading.

Document Engine streamingLinearized downloading
Fast initial page rendering
Only requires HTTPS server
Read-write while downloading
Only downloads the required data
Any PDF supported without preparation

Enable linearized downloading

The viewer keeps PDFs read-only until the download finishes. You can view the content during the download, but you can’t add annotations or make other changes.

To enable linearized downloading, follow these steps:

  1. Linearize your PDF. For instructions, refer to the Document Engine linearization or .NET SDK linearization guide. Third-party tools sometimes call linearization “fast web loading.”
  2. Upload the linearized PDF to your HTTPS server.
  3. Load the PDF with NutrientViewer.load.

The viewer enables the allowLinearizedLoading option by default. Use the following configuration:

NutrientViewer.load({
container: "#container",
document: "https://example.com/path/to/linearized.pdf",
});

To disable linearized downloading, set allowLinearizedLoading: false.

Dynamic font loading and font substitution continue to work together while a linearized PDF downloads. They help avoid font fallbacks when the viewer first renders a partially downloaded PDF. For details, refer to the dynamic font loading guide and the font substitution guide.

How the viewer requests a document

The viewer requests the PDF data in this order:

  1. The viewer requests the first 64 KB, or the whole file if it’s smaller. This request has no time limit, so your server has no deadline to prepare the document.
  2. In Web SDK 1.23 and later, the viewer confirms the file’s length with small range requests. When the server exposes Content-Range, one request asks for the last byte and the byte after it. Otherwise, the viewer sends two single-byte requests in parallel. A ranged HEAD request provides a fallback.
  3. The viewer sends up to two larger requests in parallel. These requests fetch the remaining data for the first page and the end of the file.

In Web SDK 1.25 and later, the viewer skips the first-page request when all three conditions apply:

  • The viewer opens at the first page.
  • The first page’s data exceeds 1 MB.
  • The first page’s data exceeds half the file’s size.

In this case, the viewer fetches only the data that the first page draws.

Server requirements

The viewer uses HTTP range requests(opens in a new tab) (RFC 9110). For cross-origin PDFs, it also uses the cross-origin resource sharing (CORS) protocol(opens in a new tab) (Fetch Standard).

Configure your server to handle these requests:

  • Answer range requests with 206 Partial Content(opens in a new tab). Include Content-Type: application/pdf. Return the full requested range, and don’t cap its size.
  • Answer a range that starts at or past the file’s end with 416 Range Not Satisfiable(opens in a new tab). When the server hides Content-Range, this response confirms the file’s length if the viewer can’t use a ranged HEAD request. For example, S3 presigned GET URLs reject HEAD requests. A ranged HEAD response with a matching Content-Length also confirms the length.
  • Expose Content-Range to enable the viewer to confirm the length with one request. The requested range starts inside the file and extends past its end. Shorten the range to the file’s end(opens in a new tab). Return 206. For example, a 1,000-byte file contains bytes zero through 999. A request with Range: bytes=900-1099 returns the last 100 bytes and Content-Range: bytes 900-999/1000.
  • Stream the bytes instead of buffering the whole range. In Web SDK 1.23 and later, requests that check the length share a 15-second budget, including any HEAD fallback. Each larger request in step three has 15 seconds for its status and headers to arrive. If either request times out, the viewer uses smaller range requests without a console warning. Later chunk requests have no timeout.

The first request must succeed, including CORS checks for a cross-origin PDF. If that request returns an HTTP error or fails CORS checks, the viewer stops loading the document without a whole-file fallback. An HTTP error on a later chunk request also stops loading.

The following cases trigger a whole-file download instead of progressive loading:

WhenWhat happens
The viewer can’t confirm the file’s length from the range requests or HEAD fallback (step two)The viewer downloads the whole file before displaying the first page. The browser console shows a warning starting with Linearized loading disabled. The warning names the response that didn’t match.
The content type isn’t application/pdfThe viewer downloads the whole file before displaying the first page.

The viewer sends every request with credentials: 'same-origin', so it doesn’t send cookies to a cross-origin PDF server. Cookie-protected cross-origin PDFs fail to load. For header-based authentication, use the httpHeaders option. Include those headers in Access-Control-Allow-Headers.

For cross-origin PDFs, the browser enables the viewer to read only responses with CORS headers. Configure these headers:

Configure your server

Most web servers and frameworks handle range requests, including 416 responses, when serving a file or seekable stream. The following examples also add CORS headers for cross-origin PDFs. Replace https://your-app.example.com with your application’s origin.

Web servers

Both nginx and Apache answer range requests for static files, including 416 responses. Include Access-Control-Allow-Origin exactly once per response. Add CORS headers in either the web server or the application, not both.

Serve PDF files directly — Use add_header(opens in a new tab) without the always option. For generated 416 responses, nginx keeps these headers. The always option would add them twice:

location ~ \.pdf$ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "https://your-app.example.com";
add_header Access-Control-Allow-Methods "GET, HEAD";
add_header Access-Control-Allow-Headers "Range";
add_header Access-Control-Max-Age 600;
return 204;
}
add_header Access-Control-Allow-Origin "https://your-app.example.com";
add_header Access-Control-Expose-Headers "Content-Range";
}

Proxy requests to an application — Use add_header with the always option. This adds the headers to the application’s 416 responses. If the application already sends CORS headers, don’t add them in nginx:

location /documents/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "https://your-app.example.com";
add_header Access-Control-Allow-Methods "GET, HEAD";
add_header Access-Control-Allow-Headers "Range";
add_header Access-Control-Max-Age 600;
return 204;
}
proxy_pass http://your-backend;
add_header Access-Control-Allow-Origin "https://your-app.example.com" always;
add_header Access-Control-Expose-Headers "Content-Range" always;
}

Application frameworks

If your application serves the document, use the framework’s range handling. If your endpoint generates the document, implement range handling as described in the server requirements section.

In Express, express.static and res.sendFile answer range requests, including 416 responses. Add CORS headers with the cors middleware(opens in a new tab):

const express = require("express");
const cors = require("cors");
const app = express();
app.use(
"/documents",
cors({
origin: "https://your-app.example.com",
methods: ["GET", "HEAD"],
allowedHeaders: ["Range"],
exposedHeaders: ["Content-Range"],
maxAge: 600,
}),
express.static("/path/to/documents"),
);

Check server support

Use curl to check that the server answers the viewer’s requests. The following commands send GET requests with a Range header, like the viewer does.

A HEAD request isn’t a reliable test because some servers reject it even when range requests work. For example, S3 presigned URLs support specific HTTP methods. A URL signed for GET requests rejects HEAD requests.

First, check that the server accepts range requests. Expect a 206 response:

Terminal window
curl -r 0-1 https://example.com/path/to/linearized.pdf -o /dev/null -w "%{http_code}\n"

Next, check the requests that confirm the file’s length. Replace <size> with the file’s size in bytes. Replace <size-1> with that size minus one:

Terminal window
# Expect 206 with Content-Range: bytes <size-1>-<size-1>/<size>
# and Content-Type: application/pdf when Content-Range is exposed.
curl -s -o /dev/null -D - -r "<size-1>-<size>" https://example.com/path/to/linearized.pdf
# When Content-Range is hidden, check the two single-byte requests instead.
# Expect 206 with Content-Type: application/pdf.
curl -s -o /dev/null -D - -r "<size-1>-<size-1>" https://example.com/path/to/linearized.pdf
# Expect 416.
curl -s -o /dev/null -D - -r "<size>-<size>" https://example.com/path/to/linearized.pdf

For a cross-origin PDF, add -H "Origin: https://your-app.example.com" to each command. Check that every response includes Access-Control-Allow-Origin.

Additional information

Keep these limits and storage settings in mind:

  • Linearized downloading enables viewing during downloads, not editing. You can’t modify the PDF until the download finishes.
  • Linearized downloading applies only to PDFs that the viewer downloads from an external web server.
  • Nutrient Web SDK manages temporary storage for downloads in the browser. For larger eligible PDFs, it can use the browser’s origin private file system (OPFS)(opens in a new tab). This reduces peak browser RAM usage during downloads. To disable this behavior, set tempStorage: "memory". For details, refer to the temporary storage guide.