Skip to main content
All fixes

Page speed, redirects, Core Web Vitals

Fix FCP slow: get First Contentful Paint under 1.8s

First Contentful Paint above 1.8s means users stare at a blank page too long. Eliminate render-blocking resources and inline critical CSS to make the page paint sooner.

What's happening

First Contentful Paint marks when the browser renders the first piece of DOM content — text, image, SVG, non-blank canvas. Web.dev's thresholds are under 1.8 seconds for good, 1.8-3.0 for needs improvement, and over 3.0 seconds for poor. FCP is a Lighthouse Performance metric (10% weight in the score) but isn't a Core Web Vital itself. It still matters because it sets a ceiling on perceived load speed.

FCP includes everything from request start through the first paint: TTFB, parse time for the HTML head, blocking CSS download and parse, and any blocking JavaScript before the first content. Lighthouse's FCP audit shows the breakdown and flags specific opportunities like "Eliminate render-blocking resources" and "Reduce initial server response time."

The classic FCP killer in 2026 is still a render-blocking external CSS file plus a render-blocking JavaScript bundle in the head. Modern frameworks have largely fixed this with code-splitting and CSS modules, but custom themes, third-party widgets, and analytics scripts loaded synchronously still tank FCP routinely.

Why it matters

FCP feeds into the Lighthouse Performance score, which Google uses as part of the page experience signal. A poor FCP cascades into poor LCP and poor INP, since both depend on the page actually painting first.

User-facing, slow FCP looks like a stuck loading state. Users without explicit progress indication assume the site is broken at around 3 seconds. Mobile traffic is most affected — a 4G connection in a basement adds 600-1000ms over the same connection on the street, and FCP is where that hits hardest.

Common causes

  • Render-blocking external CSS in the document head.
  • Synchronous third-party JavaScript (analytics, chat widgets) before content.
  • Web fonts loaded with font-display: block (the default), causing FOIT.
  • Large HTML documents that take 200ms+ to parse before any content is visible.
  • Server-side rendering that buffers the entire response instead of streaming.
  • DNS lookups and connection setup to multiple third-party origins on first paint.
  • Heavy CSS-in-JS frameworks injecting styles via JavaScript at runtime.

Detect this on your site

Run a quick scan with the Speed Test. The tool surfaces this exact issue with the records and context needed to apply the fix below.

Open Speed Test

How to fix it

  1. 1

    Run Lighthouse and read the FCP opportunities

    Lighthouse's FCP audit lists specific blockers — "Eliminate render-blocking resources," "Minify CSS," "Reduce server response times." Each opportunity quantifies the savings. Don't optimize blindly — fix the items at the top of the list first.

  2. 2

    Inline critical CSS in the document head

    Extract above-the-fold CSS (header, hero, navigation) and inline it in in. Defer the rest with. Tools like Critical or beasties (Next.js's built-in critical CSS extractor) automate this.

  3. 3

    Defer all non-critical JavaScript

    Audit every tag. Add defer to scripts that need the DOM but not synchronously, async to scripts that don't depend on the DOM. Move analytics and chat widgets to onload via the Next.js Script component with strategy="lazyOnload" or strategy="afterInteractive".

  4. 4

    Use font-display: swap or optional

    Set font-display: swap in @font-face to render text immediately in the fallback font, then swap when the web font loads. Use optional if you can tolerate the fallback for late-loading fonts. Both eliminate the FOIT period that delays first text paint.

  5. 5

    Stream HTML responses

    Don't buffer SSR output. Next.js App Router streams by default; for custom Express servers use res.write() with chunked encoding. Streaming lets the browser start parsing and painting before the server finishes generating the response.

  6. 6

    Preconnect to critical third-party origins

    Add for any origin loaded on the critical path. Preconnect saves 100-300ms on cold connections by warming up DNS, TCP, and TLS in parallel with HTML parsing.

  7. 7

    Validate with WebPageTest filmstrip

    WebPageTest's filmstrip view shows exactly when the first content appears, frame by frame. Compare before and after — if the first non-blank frame moves earlier by 500ms+, the fix worked. Repeat for slow 4G and a mid-tier Android emulator.

Example

<head>
  <!-- Inline critical CSS -->
  <style>
    body { font-family: system-ui, -apple-system, sans-serif; }
    .hero { min-height: 60vh; background: #0a0a0a; color: #fff; }
  </style>

  <!-- Defer non-critical CSS -->
  <link rel="preload" href="/full.css" as="style"
        onload="this.onload=null;this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="/full.css"></noscript>

  <!-- Preconnect to font origin -->
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

  <!-- Defer all third-party JS -->
  <script defer src="/analytics.js"></script>
</head>

Critical CSS inlined, non-critical deferred, preconnect for fonts.

Frequently asked

No. FCP is a Lighthouse Performance metric (10% weight) but not part of Core Web Vitals. The CWV trio is LCP CLS and INP. FCP still matters as a leading indicator and for perceived performance.

FCP is when the browser paints the first content of any kind. LCP is when the largest above-the-fold element finishes painting. FCP is always less than or equal to LCP. Optimizing FCP often improves LCP but not always.

Yes for content-driven pages. SSR delivers HTML the browser can paint immediately while client-side rendering shows a blank shell until JS hydrates. For app-style routes behind a login the trade-off is more nuanced.

Related fixes