How to check page size

Run curl -s -o /dev/null --compressed -w '%{size_download}' <url> for the bytes that cross the wire, then the same command without --compressed for the bytes the browser parses. On an MDN article that was 30865 against 258732, a ratio of 8.4 to 1.

Why check this

Page weight is a release gate on any product with mobile users or a data budget. Check it when a build adds a library, when an image pipeline changes, and on staging before sign-off. The failure it prevents is specific: a bundle ships uncompressed because one route serves it from a path the compression rule misses, and the document grows eight times with no visible change.

Two numbers answer different questions. The compressed size predicts transfer time on a slow link. The uncompressed size predicts parse and memory cost on a weak device.

Prerequisites

Steps

  1. Step 1.

    Read the size the server declares in its headers.

    curl -sI --compressed https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
    
    HTTP/2 200
    x-goog-stored-content-encoding: identity
    x-goog-stored-content-length: 258732
    content-encoding: br
    content-length: 30865

    content-length counts the compressed bytes, because content-encoding is br. The stored length is the same document before compression.

  2. Step 2.

    Measure the bytes that actually arrive, headers separated from body.

    curl -s -o /dev/null --compressed -w 'headers %{size_header} bytes\nbody    %{size_download} bytes\n' https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
    
    headers 3062 bytes
    body    30865 bytes

    The body matches content-length. The 3062 bytes of headers cross the wire too and no size header counts them.

  3. Step 3.

    Repeat without --compressed to get the size the parser sees.

    curl -s -o /dev/null -w 'headers %{size_header} bytes\nbody    %{size_download} bytes\n' https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
    
    headers 3033 bytes
    body    258732 bytes

    Dropping the Accept-Encoding offer makes the server send the document uncompressed, so this is the decoded size.

  4. Step 4.

    For the whole page, not the document alone, open DevTools on the same URL, Console tab, and read the navigation entry.

    const n = performance.getEntriesByType('navigation')[0];
    console.table({
      'navigation.transferSize': n.transferSize,
      'navigation.encodedBodySize': n.encodedBodySize,
      'navigation.decodedBodySize': n.decodedBodySize,
      'sum of resource transferSize': performance.getEntriesByType('resource').reduce((s, r) => s + r.transferSize, 0),
    });
    
    navigation.transferSize        0
    navigation.encodedBodySize     30865
    navigation.decodedBodySize     258732
    sum of resource transferSize   1692

    encodedBodySize and decodedBodySize agree with curl to the byte. transferSize reads 0 and the sum over subresources reads 1692, which is the trap described below.

How to read the result

| What you see | What it means | What to do | | --- | --- | --- | | Compressed body far below the decoded body | Compression is working on this route | Record the ratio. 8.4 to 1 on the document above. | | The two measurements are equal | Nothing is compressed | Check the encoding negotiation in How to check content-encoding. | | No content-length in the headers | The response is streamed or chunked | Use %{size_download} from a GET. A HEAD cannot tell you the size here. | | transferSize is 0 | The load came from the HTTP cache | Reload with "Disable cache" ticked, or the number is meaningless. |

Common mistakes

Sign: Page weight is reported as a few kilobytes and nobody can reproduce the slow load.Cause: On a load served from the HTTP cache, navigation.transferSize reads 0 and the sum of resource transferSize collapsed to 1692 bytes in this capture, against 30865 bytes of document alone. That is an understatement of roughly 150 times. Tick Disable cache, or read encodedBodySize instead.
Sign: The document size is quoted as the page size.Cause: curl fetches one URL and stops. The MDN article above is 30865 bytes of HTML and pulls 104 further requests. A document-only figure is a valid number for the HTML budget and a wrong number for the page budget.
Sign: A HEAD request returns no content-length and the check is recorded as failed.Cause: www.cloudflare.com answers a HEAD with content-encoding and no content-length, because the response is generated as it is sent. The size still exists: a GET of that page downloaded 105207 bytes with compression on.

What to check next

FAQ

How to check the content-length header?

Send a HEAD and read it: curl -sI --compressed <url> | grep -i content-length. When content-encoding is present, the value counts compressed bytes. When the response is chunked the header is absent, and a GET with %{size_download} is the only measurement left.

Does content-length include the headers?

No. It counts the body. On the document above the headers added 3062 bytes on top of a 30865 byte body, which matters on a page with many small responses.

Why does DevTools show a different size than curl?

DevTools reports the whole page by default, every request together, while curl reports one URL. The panel also counts header bytes in the transferred column.

Which number belongs in a performance budget?

Both, labelled. Compressed bytes for transfer time, decoded bytes for parse and memory cost on the device.

Verified

Verified by Maks Vernycurl 8.21.0Chrome DevTools capture of 2026-09-11

Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.

basic5 minpublished updated Maks Verny