Skip to main content
CodeRocket
Performancehighweb-vitals

Optimize first contentful paint

rule · first-contentful-paint

First Contentful Paint marks when the browser renders the first piece of content.

Code Example

TEXT
HTML → DOM → CSSOM → Render Tree → Paint
 
Blocking resources delay this path:
- Synchronous JavaScript
- Render-blocking CSS
- Large HTML documents

Why It Matters

FCP is the first visual signal that a page is loading—fast FCP reassures users that the site is responding, reducing perceived wait time and bounce rates.

FCP Score Thresholds

ScoreRatingUser Perception
0–1.8sGoodPage is responsive
1.8–3sNeeds improvementNoticeable delay
> 3sPoorUser may abandon

Eliminate Render-Blocking Resources

HTML
<!-- Bad: Render-blocking -->
<head>
  <link rel="stylesheet" href="/all-styles.css">
  <script src="/app.js"></script>
</head>
 
<!-- Good: Non-blocking -->
<head>
  <!-- Inline critical CSS -->
  <style>
    /* Critical above-fold styles */
    body { margin: 0; font-family: system-ui; }
    .header { height: 60px; background: #fff; }
    .hero { min-height: 400px; }
  </style>
 
  <!-- Defer non-critical CSS -->
  <link rel="preload" href="/styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="/styles.css"></noscript>
 
  <!-- Defer JavaScript -->
  <script src="/app.js" defer></script>
</head>

Preconnect to Critical Origins

HTML
<head>
  <!-- Establish early connections -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link rel="preconnect" href="https://cdn.example.com" crossorigin>
 
  <!-- DNS prefetch for less critical origins -->
  <link rel="dns-prefetch" href="https://analytics.example.com">
</head>

Optimize Server Response Time

JavaScript
// Next.js - reduce TTFB with caching
// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          {
            key: 'Cache-Control',
            value: 'public, s-maxage=3600, stale-while-revalidate=86400',
          },
        ],
      },
    ]
  },
}
TypeScript
// Use streaming for faster first byte
// app/page.tsx (Next.js App Router)
import { Suspense } from 'react'
 
export default function Page() {
  return (
    <main>
      {/* Static content renders immediately */}
      <Header />
      <Hero />
 
      {/* Dynamic content streams in */}
      <Suspense fallback={<ProductSkeleton />}>
        <Products />
      </Suspense>
    </main>
  )
}

Inline Critical CSS

JavaScript
// Build tool to extract and inline critical CSS
// Using critical package
const critical = require('critical')
 
critical.generate({
  base: 'dist/',
  src: 'index.html',
  target: 'index-critical.html',
  inline: true,
  width: 1300,
  height: 900,
  penthouse: {
    blockJSRequests: false,
  },
})

Font Loading Optimization

HTML
<!-- Preload critical fonts -->
<link
  rel="preload"
  href="/fonts/main.woff2"
  as="font"
  type="font/woff2"
  crossorigin
>
CSS
/* Use font-display to avoid blocking text */
@font-face {
  font-family: 'MainFont';
  src: url('/fonts/main.woff2') format('woff2');
  font-display: swap; /* Show fallback text immediately */
}

React Server Components

TSX
// Server components render faster - no client JS needed
// app/components/Hero.tsx
export default function Hero() {
  // This component renders on server, no hydration delay
  return (
    <section className="hero">
      <h1>Welcome to Our Site</h1>
      <p>Content visible immediately</p>
    </section>
  )
}
 
// app/page.tsx
import Hero from './components/Hero'
 
export default function Page() {
  return (
    <>
      <Hero /> {/* Fast FCP - server rendered */}
      <InteractiveSection /> {/* Client component, loads after */}
    </>
  )
}

Measuring FCP

JavaScript
// Using web-vitals library
import { onFCP } from 'web-vitals'
 
onFCP(metric => {
  console.log('FCP:', metric.value, 'ms')
 
  // Report to analytics
  if (metric.value > 1800) {
    console.warn('FCP exceeds 1.8s threshold')
  }
})
 
// Using Performance API
const observer = new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    if (entry.name === 'first-contentful-paint') {
      console.log('FCP:', entry.startTime)
    }
  }
})
observer.observe({ type: 'paint', buffered: true })

Verification

Automated Checks

  • Run Lighthouse—check FCP in Performance section
  • Use Chrome DevTools Performance panel
  • Test with network throttling (Slow 3G)
  • Check PageSpeed Insights for field data
  • Use WebPageTest for detailed waterfall analysis

Manual Checks

  • Confirm the user-facing outcome manually on a throttled target device or browser profile so the optimization still improves the real experience.