Googlebot indexes only the first 2MB of your HTML document, and Google measures that ceiling on the uncompressed byte stream - the HTML Googlebot decompresses and parses, not the gzipped or brotli-compressed payload that crosses the wire. That single accounting detail is why a page that transfers in ~200KB can blow a 2MB ceiling it never appears to approach. When it does, Googlebot stops the fetch at the cap and sends only the bytes it already downloaded for indexing - the tail of your document, often your JSON-LD, your canonical, your main content, is simply never parsed.
Your page can return a clean HTTP 200, render perfectly in a browser, and still hand Google a document that stops halfway through. This is how you probe for it, and how you fix it.
The 2MB Ceiling Is Measured After Decompression, Not on the Wire
Googlebot's indexing limit is the first 2MB of a supported file type, per fetched document. The crawler streams your response, decompresses it, and processes bytes until it reaches the cap; once it hits the limit, Googlebot stops the fetch and forwards only the already-downloaded portion for indexing consideration. The limit applies to the uncompressed data of the raw HTML resource Googlebot fetches - the response body - not the rendered DOM and not the total weight of the page including images, scripts, and stylesheets.
15MB Fetch vs. 2MB Index: What Google Actually Documents
Google documents two distinct limits, and conflating them is the most common error in the discourse around this topic. The 15MB figure is a fetch/download default that applies across all of Google's crawlers - the maximum Googlebot will download for any single resource. The 2MB figure is the Googlebot-for-Search indexing limit on supported file types such as HTML: the slice Google actually processes for the index. PDFs get a separate 64MB ceiling. Google moved these numbers into its broader crawler documentation in early 2026 and described the update as a documentation clarification, not a change in crawler behavior - and its own changelog does not spell out how the 15MB and 2MB figures interact. So the operative number for whether your structured data and main content get indexed is 2MB of uncompressed HTML, and it always was; what changed is that the limit is now documented plainly.
The cap is per-resource. It governs the single HTML document Googlebot pulls for a URL. Subresources - your CSS files, JavaScript bundles, fonts, and images - are fetched as separate requests and each is bound by the same per-file limit. The 2MB ceiling is not a total-page-weight budget. It is a hard wall on one document.
Transfer Size vs. Parsed Size: Why 200KB Can Be 2.4MB
The transfer size you read in the Network tab is the compressed size; the size Googlebot meters is the decompressed size, and HTML compresses brutally well. Gzip and brotli excel at exactly the redundancy that bloats modern HTML - repeated class names, repeated inline-SVG path data, the structural sameness of server-rendered markup. Compression ratios in the 8:1 to 12:1 range are typical for verbose HTML, so a document that arrives as 200KB over the wire can decompress to roughly 1.6MB-2.4MB in Googlebot's parser. Treat those numbers as an illustration of the gap, not a fixed rule - your ratio depends entirely on your markup.
This is the trap. Every instrument a developer reaches for first - the browser Network panel, a CDN dashboard, a curl -s url | wc -c against a compressed response - reports the wire size. None of them report the number that decides whether your tail gets indexed. You are measuring the wrong byte stream, and the wrong byte stream always looks safe.
To see the real number, disable content-encoding on the request or measure the body after decompression:
curl -s -H 'Accept-Encoding: gzip' --compressed https://yoursite.com/your-page | wc -c
--compressed tells curl to negotiate gzip on the wire and hand you the decompressed bytes, so wc -c reports the size Googlebot actually processes - not the size your CDN bills you for.
Probe your decompressed page size →
What Googlebot Does at the Cutoff: Silent Mid-Document Truncation
When a document exceeds 2MB, Googlebot stops the fetch at the cap and indexes only the bytes it already pulled - there is no error, no warning, and no signal in the response status. The fetch returns HTTP 200. The truncation happens inside Google's pipeline, after a successful transfer, on bytes you served correctly. This is the defining property of the failure: it is silent. It does not look like a server error, a robots.txt block, or a noindex directive - all of which leave a trace you can probe. Truncation leaves nothing.
What gets lost is whatever you placed late in the document:
- JSON-LD structured data, if your
<script type="application/ld+json">block sits near the closing</body>- a common pattern in SSR frameworks that append schema at the end. Cut here, your Article, Product, or FAQ schema is gone, and with it your eligibility for rich results. - The canonical tag and meta robots, if a templating quirk or late hydration injected them below the fold. A stranded canonical means Google falls back to its own URL choice; a truncated
noindexyou intended to enforce never registers. - The tail of your main content - the back half of a long article, the lower rows of a product grid - never enters the index because it was never parsed.
The distinction matters: truncation hurts both rankings and rich results, and they are separate injuries. Lost JSON-LD kills your rich-result eligibility without necessarily moving your blue-link rank. A stranded canonical or missing main content damages indexation itself. One silent failure, two distinct blast radii.
How to Detect Silent Truncation Before It Costs You Rankings
Detection requires measuring the decompressed document and confirming the tail survived - because the failure emits no signal, you instrument for it deliberately. Three probes, run in order, isolate the problem with certainty.
Measure the Decompressed Size, Not the Network Tab
Start by capturing the uncompressed byte count of the raw HTML document, because that is the only number the 2MB cap reads. Use the --compressed curl one-liner above, or a tool that decompresses and weighs the parsed body for you. The threshold is concrete: any HTML document over 2MB decompressed is at risk, and anything within ~10% of that ceiling should be treated as a live regression hazard - SSR output and content both grow over time. Most pages clear this easily; Google reports a median uncompressed HTML size near 20KB, with even the 90th percentile around 392KB. The pages at risk are the heavy, inline-everything outliers - and those are exactly the ones whose owners assume they are safe.
If your decompressed size sits suspiciously close to your transfer size, your second job is to confirm compression is even being negotiated - an uncompressed response is its own 2MB risk, covered below.
Test your Gzip/Brotli negotiation →
Read the Search Console Crawl Stats Signal
Google Search Console's Crawl Stats report exposes the second signal: average response size by page. Open Settings → Crawl stats and inspect the size distribution. Documents that Google records as unusually large, or a host whose average fetched size has crept upward across deployments, point directly at decompressed-byte bloat. Crawl Stats will not announce "truncated" - Google does not surface that label - but a rising fetched-size trend is the macro fingerprint of the same problem, and it correlates with crawl budget being spent on bytes that never get indexed.
Confirm the Tail Survived: Inspect Canonical, Meta, and JSON-LD Parse
The decisive test is whether your late-document tags are still being parsed, so probe the canonical, meta robots, meta description, and JSON-LD as Google would see them in the raw, pre-render HTML - not the live DOM. This is the trap inside the trap: your browser's rendered DOM shows every tag intact, because the browser has no 2MB cap and parses the whole document. Googlebot's truncation happens on the fetched response, before render. View source, or fetch the raw HTML, and confirm the structured data block and head tags land within the first 2MB, not merely present in the rendered page.
Audit your meta and canonical tags →
Find the Bloat: Inlined CSS, base64 SVG/Images, SSR Payload
Most 2MB overruns trace to four culprits that inflate decompressed size while staying invisible in the rendered output:
- Inlined CSS. A full design system pasted into a
<style>block - often the whole framework, not the critical path - adds hundreds of KB of decompressed text per page. - base64-encoded images and SVG. A data-URI image is ~33% larger than the binary file and lives inside the HTML document, counting against the 2MB cap instead of being fetched as a separate subresource. A handful of inlined hero images alone exhausts the budget.
- SSR payload and serialized hydration state. Frameworks serialize application state into the HTML for client hydration - Angular TransferState, Next.js
__NEXT_DATA__, and equivalents. A verbose API response embedded for hydration dwarfs the visible content. - Large inline
<style>and repeated markup. Server-rendered lists and tables with verbose, repeated class strings compress well on the wire and decompress huge - exactly the gzip illusion that hides the problem.
How to Fix Pages That Blow the 2MB Limit
Remediation is two moves: get the critical tags inside the first 2MB, then strip enough weight that nothing important falls outside it. Execute them in this order - front-loading is the immediate safety net; weight reduction is the durable fix.
- Front-load every indexation-critical tag. Canonical, meta robots, meta description, hreflang, and JSON-LD belong in the
<head>or the early<body>- never appended near</body>. - Externalize what does not need to be inline. Move CSS to linked stylesheets and images to real
<img src>subresources so they leave the HTML document and stop counting against the cap. - Trim the SSR payload. Serialize only the state hydration actually needs.
- Verify compression is negotiated so you are not paying the uncompressed penalty on the wire as well.
Move Critical Tags Above the 2MB Mark
The single highest-leverage fix is placement: relocate the canonical, meta robots, meta description, and JSON-LD into the document head and the opening of the body. Googlebot parses top-to-bottom and stops at 2MB, so the first 2MB always wins. A tag placed in the opening hundreds of KB is guaranteed to be parsed regardless of how bloated the tail becomes. This converts a hard failure into a graceful degradation: if the document still overshoots, you lose tail content - recoverable, lower-stakes - instead of losing the structured data and directives that govern how the whole page is indexed.
Strip the Weight: Externalize CSS, Defer base64, Trim SSR Output
Reduce the decompressed document until it clears the ceiling with margin. Pull inlined CSS into linked stylesheets and ship only critical CSS inline. Replace base64 data URIs with referenced image files - a base64 hero is a triple penalty: ~33% larger than the binary, parsed as HTML, and counted against the document cap instead of fetched as a subresource. Audit your SSR framework's serialized state and prune embedded API responses to the fields hydration requires. Each move shifts weight out of the 2MB document and into separately-accounted subresources, which is exactly where it stops threatening crawlability.
Verify Compression Is Actually Negotiated
Confirm your server returns Content-Encoding: gzip or br for the HTML document, because an unencoded response transfers at full decompressed size and is its own performance liability. Compression does not move the 2MB ceiling - Google meters the decompressed bytes either way - but verifying it closes the loop on your detection math and keeps the wire size you read elsewhere honest.
Test your compression negotiation →
Automate the 2MB Check in CI/CD
Truncation is a regression, not a one-time bug - every deployment that adds inline CSS, embeds a new schema block, or fattens the SSR payload pushes a previously-safe page over the cap. Treat the 2MB ceiling like any other deploy gate: probe it on every build, before it ships. Dispatch the UpMonitor CLI in your pipeline to weigh the decompressed document and audit crawlability on each release.
npx @upmonitor/cli check https://yoursite.com --checks pageWeight,compression,seoMeta
Wire this into your CI/CD step before the deploy promotes, and a page that crosses 2MB fails the build instead of failing silently in Google's index three crawls later. The instrument runs in seconds; the alternative is discovering the truncation weeks later as a quiet ranking and rich-result decay you cannot explain.
Conclusion
Googlebot's 2MB indexing limit is a precise, silent, decompressed-byte ceiling - not a network-size budget, not a total-page-weight figure, and not something that ever announces itself in an HTTP status. It is a distinct limit from the 15MB fetch default, and it is the one that decides what reaches the index. The mechanic is decompression-time truncation; the detection is measuring the parsed size and confirming your tail tags survived inside the first 2MB; the fix is front-loading critical tags and stripping inline weight out of the document. Run those three movements once, then automate the check so a deploy never reintroduces the failure. For a broader pass on crawlability, sitemaps, and meta hygiene around this same document, work through the technical SEO audit guide.