← Back to Blog
Performance

How I Cut First Contentful Paint (FCP) on My Next.js Blog

PerformanceNext.jsReact
Sandeep Kumar4 min read · July 1, 2026
How I Cut First Contentful Paint (FCP) on My Next.js Blog

I ran my own blog through PageSpeed Insights and the mobile score wasn’t where I wanted it — the culprit was a slow First Contentful Paint. FCP is the moment a user first sees *anything* render, and on mobile it’s often the difference between someone reading your post and someone bouncing. Here’s exactly how I improved FCP in my Next.js (React) app, with the real changes that moved the needle — no generic advice.

What is First Contentful Paint (FCP)?

First Contentful Paint (FCP) is a Core Web Vital that measures how long it takes — from the moment a page starts loading — for the browser to render the first piece of content on screen, whether that’s text, an image, or any non-blank element. In short, it’s when the user first sees the page respond. A good FCP is 1.8 seconds or less.

It’s easy to confuse FCP with Largest Contentful Paint (LCP), so here’s the difference: FCP is "something appeared," while LCP is "the biggest above-the-fold thing appeared." Both are Core Web Vitals, and on a text-heavy page they’re usually driven by the same factors — how fast your HTML, CSS, and fonts arrive and render.

Rule zero: measure before you touch anything

I don’t guess at performance — I measure, change one thing, measure again. Use PageSpeed Insights or Chrome DevTools Lighthouse (mobile profile, since mobile is almost always the bottleneck), and watch the real Web Vitals in Search Console over time.

The mistake I see most often: optimizing the thing that *feels* slow instead of the thing the trace says is slow. Let the Lighthouse "Opportunities" and "Diagnostics" sections point you.

Ship less JavaScript

React apps get slow when they ship too much JS to the client — the browser has to download, parse, and execute it before the page is interactive, and that competes with your first paint. In the Next.js App Router, components are Server Components by default. Keep it that way, and push "use client" down to the leaves that actually need interactivity.

good-vs-bad.tsx
// ❌ whole page becomes client JS just for one button
'use client';
export default function Page() { /* ...lots of static markup... */ }

// ✅ page stays a Server Component; only the button ships JS
export default function Page() {
  return <><StaticContent /><LikeButton /></>; // LikeButton is 'use client'
}

For anything below the fold or rarely used, load it lazily so it isn’t in the critical path:

lazy.tsx
import dynamic from 'next/dynamic';
const CommentsWidget = dynamic(() => import('./CommentsWidget')); // not in first paint

Tame your fonts — the quiet FCP killer

This is the one that surprised me most on my own site. Web fonts block text from painting, and I was loading more fonts than I actually used. Two fixes made a real difference.

1. Delete fonts you don’t use. I had a decorative font declared that no CSS actually referenced — a pure wasted download on every page. Audit what’s really used and remove the rest.

2. Only preload the font on the critical path. Your heading font should load first; a code/label font can wait. With next/font, self-host, subset, and set preload: false on non-critical fonts so they don’t compete with the one your first paint needs.

app/layout.tsx
import { Plus_Jakarta_Sans, Geist_Mono } from 'next/font/google';

// heading/body font — on the critical path, preloaded by default
const sans = Plus_Jakarta_Sans({ subsets: ['latin'], display: 'swap' });

// code font — not needed for first paint, so don't preload it
const mono = Geist_Mono({ subsets: ['latin'], display: 'swap', preload: false });

display: "swap" shows your text immediately in a fallback font and swaps when the web font arrives — so text paints fast instead of waiting. next/font also self-hosts the files (no extra connection to Google) and reserves space to avoid layout shift.

Kill render-blocking resources

Anything the browser must fetch and process before it can paint delays FCP. The usual offenders:

  • Third-party scripts — load analytics and tags with next/script and strategy="afterInteractive" (or lazyOnload) so they never block the first paint.
  • Big CSS — ship only what the page needs; CSS Modules keep styles scoped and tree-shakeable.
  • Expensive paint effects — heavy backdrop-filter: blur() and large filter: blur() backgrounds are surprisingly costly on mobile GPUs. Use them sparingly above the fold.
app/layout.tsx
import Script from 'next/script';

// deferred — doesn't block first paint
<Script src="https://www.googletagmanager.com/gtag/js?id=..." strategy="afterInteractive" />

Optimize the above-the-fold path

FCP and LCP both live above the fold, so make that region cheap to render:

  • Use next/image and add priority to your one above-the-fold image so it isn’t lazy-loaded.
  • Give images explicit dimensions (or an aspect-ratio) so they don’t shift layout as they load.
  • Keep the hero simple — text paints faster than images, and a lighter hero means a faster FCP.

Serve it static, from the edge

The fastest response is one that’s already rendered. Use static generation (SSG) or incremental static regeneration (ISR) for content pages so the HTML — and your first paint — arrives instantly from a CDN instead of being built per request.

app/blog/[slug]/page.tsx
export const revalidate = 60; // ISR: serve static HTML, refresh in the background

I go deeper on the caching side of this in Next.js App Router caching, finally explained, and on the broader ranking picture in the technical SEO checklist.

The results, and the takeaway

None of these were heroic rewrites — they were small, measurable changes: fewer fonts, less client JS, deferred scripts, static HTML. That’s the pattern with FCP. You don’t fix it with one silver bullet; you remove a handful of things standing between the user and the first paint, measuring each time.

Further reading: Abhi Venkata’s Improving First Contentful Paint (FCP) in React apps is a good companion read on the React side.

Performance isn’t a feature you add at the end. It’s the sum of small decisions about what you make the browser do before it can show the user anything.

Frequently asked questions

What is a good First Contentful Paint (FCP) score?

Google considers an FCP of 1.8 seconds or less "good," 1.8–3.0 seconds "needs improvement," and over 3.0 seconds "poor." These thresholds are measured on mobile at the 75th percentile of your real users, so optimize for the slower devices and networks, not your fast laptop.

What is the difference between FCP and LCP?

First Contentful Paint (FCP) marks when the browser renders the first content of any kind — the first sign the page is loading. Largest Contentful Paint (LCP) marks when the largest above-the-fold element (usually a heading or hero image) finishes rendering. FCP is "something appeared"; LCP is "the main content appeared."

Do web fonts slow down FCP?

Yes. Web fonts can block text from painting until the font file downloads. Reduce the impact by self-hosting and subsetting fonts (next/font does this automatically), using font-display: swap so text shows immediately in a fallback, preloading only the font on the critical path, and removing any font you declare but do not actually use.

How does React or Next.js affect FCP?

Shipping too much client-side JavaScript delays FCP because the browser must download and process it before rendering. In the Next.js App Router, keep components as Server Components by default, push "use client" to the leaves, lazy-load below-the-fold widgets with next/dynamic, and serve pages statically (SSG/ISR) so the HTML arrives pre-rendered.

How do I measure FCP?

Use PageSpeed Insights or Chrome DevTools Lighthouse (mobile profile) for lab data, and Google Search Console’s Core Web Vitals report for real-user field data. Measure before and after each change so you know what actually helped rather than guessing.

Is backdrop-filter bad for performance?

Heavy backdrop-filter or large filter: blur() effects can be expensive to paint, especially on mobile GPUs and on elements that repaint during scroll (like a sticky blurred navbar). They are fine used sparingly, but avoid large blurred regions above the fold where they can delay your first paint.