Fixing LCP on Text-Heavy Documentation Pages

Most LCP advice is about images: preload the hero, compress it, give it high priority. Documentation pages often have no hero at all. Their largest element in the first viewport is the introductory paragraph, the H1 or a code block, and none of the image advice applies. Yet documentation sites regularly fail LCP on mobile, because text has its own set of blockers — and they are easier to fix than most teams expect.

This guide shows how to confirm that your LCP element is text, why the timing then depends almost entirely on TTFB and render delay, and how to remove the three usual culprits: slow HTML delivery, render-blocking CSS and web fonts. It is part of Largest Contentful Paint Optimization for Static Sites.

Prerequisites

  • A documentation template with a slow LCP in field data or on a throttled Lighthouse run.
  • Chrome DevTools to confirm the LCP element and its breakdown — see Measuring LCP Subparts with DevTools.
  • Access to the layout's <head> and build pipeline.

Confirm the Element Is Text

Record a load in the DevTools Performance panel with a mobile profile and open the LCP insight. It names the element. On documentation pages it is usually one of three things: the first paragraph (often the largest, because paragraphs span the full column and several lines), a code block with a dark background, or the H1 on pages with a short intro.

In field data, the web-vitals attribution build's attribution.target gives the element's selector. Group LCP by selector for the docs template; if p or pre dominates, the fixes below apply.

When the element is text, the LCP breakdown collapses to two parts: TTFB and render delay. There is no resource to discover or download, so every millisecond after the first byte is time the browser spent unable to paint the text.

LCP breakdown for an image element and a text element An image LCP of 2.4 seconds splits into TTFB, load delay, load duration and render delay. A text LCP of 2.4 seconds on a documentation page has only TTFB of 0.6 seconds and render delay of 1.8 seconds, which is spent waiting for CSS and a web font. Same 2.4 s LCP, different anatomy image element text element TTFB render delay: CSS, font, JS TTFB load delay load duration render delay for text, anything after the first byte is time the page could not paint
Text LCP has no download to optimise; it has only blockers to remove.

Fix 1: Get the HTML There Fast

TTFB is the floor for text LCP. On a static site it should be low, but three things commonly inflate it:

  • HTML not cached at the edge. Many CDN setups cache assets for a year but send HTML with Cache-Control: no-cache or max-age=0, so every request goes to the origin. Cache HTML at the edge with a short s-maxage and stale-while-revalidate, and purge on deploy. See Stale-While-Revalidate for Static HTML.
  • Redirect chains. A link to /docs/install that redirects to /docs/install/ and then to a versioned path costs a round trip each. Link to the final URL everywhere and make the canonical form the one the site generator emits.
  • Large HTML documents. Some docs pages ship 500 KB of HTML because of inline SVG sprites or huge navigation trees. The first paragraph cannot paint until the browser has parsed everything before it; move large blocks after the content or trim them.

Fix 2: Stop CSS From Blocking the First Paint

Every <link rel="stylesheet"> in the <head> blocks rendering until it has downloaded and parsed. Documentation themes often load three or four: the theme, a syntax highlighting theme, a search widget's styles and an icon font.

  1. Inline what the first viewport needs. For documentation layouts, the critical CSS — reset, layout grid, header, typography and code block basics — is usually 8–15 KB uncompressed. Inline it in a <style> element. If the whole stylesheet is under about 20 KB compressed, inlining all of it is simpler and fine.
  2. Load the rest without blocking. Use <link rel="preload" as="style" onload="this.rel='stylesheet'"> with a <noscript> fallback, or media="print" onload="this.media='all'".
  3. Drop styles the page does not use. Search widget CSS can be loaded when search is opened; syntax highlighting CSS only on pages with code.
First paint with blocking stylesheets and with inlined critical CSS With four blocking stylesheets, first paint of the paragraph waits until the last one finishes at 1.6 seconds. With critical CSS inlined and the rest loaded asynchronously, the paragraph paints at 0.8 seconds, right after the HTML arrives. What the first paint waits for Four blocking stylesheets HTML theme.css, prism.css search.css, icons.css paint 1.6 s Critical CSS inlined HTML + inline style rest, non-blocking paint 0.8 s throttled phone, cold cache; LCP element is the first paragraph
Inlined CSS arrives with the HTML, so nothing stands between the first byte and the first paint.

Details and generator-specific setups are in Eliminating Render-Blocking CSS on Static Sites.

Fix 3: Do Not Let Fonts Hide Text

With font-display: block or the default auto, browsers hide text that uses a loading web font for up to three seconds. The LCP paragraph is invisible, so LCP waits for the font. This is the single biggest cause of slow text LCP on documentation sites that use a custom typeface.

Set font-display: swap or optional on every text face. With swap, the paragraph paints immediately in the fallback, and that paint counts for LCP; the later re-render in the web font is not a new LCP candidate unless the text block grows. Pair it with a metric-matched fallback so the swap does not shift layout; see Metric-Matched Fallback Fonts with size-adjust. With optional, the font is used only if it arrives within about 100 ms, which keeps both LCP and CLS stable.

If you self-host fonts, preload the body regular weight so it usually arrives before first paint; see Preloading Fonts Without Double Downloads.

Text LCP as each fix is applied Lab LCP on a throttled phone for a documentation page. Baseline 3.4 seconds. After caching HTML at the edge, 2.9 seconds. After inlining critical CSS, 2.2 seconds. After switching fonts from block to swap, 1.3 seconds. Lab LCP after each fix, throttled phone (s) good ≤ 2.5 s 3.4 baseline 2.9 + edge-cached HTML 2.2 + inline critical CSS 1.3 + font-display swap fixes applied cumulatively; LCP element was the first paragraph throughout
The font fix gave the largest single gain, and it was a one-word change.

Fix 4: Keep JavaScript Out of the Way

Client-rendered documentation — where the article body is fetched as JSON and rendered after hydration — puts the LCP paragraph behind the whole JavaScript bundle. Static site generators render content to HTML at build time, so this should not happen, but it creeps in through versioned-docs selectors, "tabs for every language" components that render nothing on the server, and content wrapped in client-only components. Check by disabling JavaScript in DevTools: the first paragraph must still appear.

Synchronous scripts in the <head> are the other source. A theme toggle script that reads localStorage to avoid a flash of the wrong theme is fine if it is tiny and inline; an analytics tag or consent manager loaded synchronously is not. Move them to defer or async, and consider the patterns in Third-Party Script Performance on Static Sites.

Watch the Element Change

Text LCP has a quirk worth knowing: the largest element can change as the page loads. A cookie banner, a large code block that renders after syntax highlighting, or a callout that appears after hydration may become the new largest element and push LCP later. In DevTools the LCP insight shows the final candidate; in the trace, the timings track shows each candidate as it was recorded. If LCP jumps to a late element, either reserve that element's space in the initial HTML or keep it smaller than the first paragraph.

Measured Impact

A 3,500-page API documentation site built with Docusaurus had mobile LCP p75 of 3.3 seconds, with the first paragraph as the LCP element on 81% of views. The team made the HTML cacheable at the edge for five minutes with stale-while-revalidate, inlined 11 KB of critical CSS, set font-display: swap with metric-matched fallbacks for the two custom fonts, and deferred the consent manager. Field LCP p75 fell to 1.7 seconds within the 28-day window, CLS stayed at 0.03, and the "poor LCP" group in Search Console emptied.

Pitfalls & Rollback

  • Optimising images on a text-LCP page. Check the element first; image work will not move a paragraph's LCP.
  • Inlining too much CSS. Inlined CSS is not cached separately and is re-sent with every page; keep it to what the first viewport needs.
  • font-display: optional with no preload. Text is fast but readers rarely see the brand font on first visit.
  • Theme scripts that block. Keep the pre-paint theme script under 1 KB and inline.
  • Rollback: each fix is independent and reversible in the layout template.

Conclusion

When the largest element is text, LCP is TTFB plus render delay, and render delay is almost always CSS, fonts or JavaScript standing between the HTML and the first paint. Cache the HTML at the edge, inline the critical CSS, make every text font swap or optional, and keep content rendering off the client. Documentation sites that do all four routinely reach LCP well under two seconds on mobile without touching a single image.

FAQ

Why is my LCP element a paragraph?

On pages without a large image in the first viewport, the largest visible block of text — often the first paragraph, a code block or the H1 — is the largest contentful element. Its size is the area of the text block, so a long intro paragraph frequently beats a heading.

What slows down LCP when the element is text?

Everything that delays the first paint of that text - slow TTFB, render-blocking stylesheets, a web font with block-period rendering, and client-side JavaScript that renders or moves the content. There is no resource download for the element itself.

Does font-display swap help text LCP?

Yes. With swap, text paints immediately in the fallback font and that paint counts for LCP. With block or auto, text is invisible for up to three seconds while the font loads, and LCP waits for it.

Should I inline critical CSS on documentation pages?

For small stylesheets, inlining the whole thing is simplest and removes the render-blocking request. For larger ones, inline the CSS for the layout, header and typography, and load the rest without blocking render.