Ship the hero in server HTML

Put the hero headline in server HTML; never fade it in from opacity: 0.

From nextjs-pagespeed.

This page’s LCP: measuring…

Three ways to ship it

Press Reload, then drag the hydration delay.

A · In the HTMLGood
B · After hydrationBad
C · HTML, fades from 0Bad

LCP2.5 s

A600 ms
B1800 ms
C1400 ms
export default function Page() {
  return (
    <section className="hero">
      <h1>Close the month in two days</h1>
      <p>Reconciled books, every month.</p>
      <SignupButton />
    </section>
  );
}

Keep motion, drop the delay

Press Replay. Only the left one starts invisible.

Fade from opacity 0Bad

Close the month in two days

+300 ms
Rise, opacity 1Good

Close the month in two days

+0 ms
@keyframes hero-rise {
  from { transform: translateY(8px); }
}
.hero h1 { animation: hero-rise 300ms cubic-bezier(0.23, 1, 0.32, 1); }

@media (prefers-reduced-motion: reduce) {
  .hero h1 { animation: none; }
}

Is it in the response?

Type a headline and compare what View Source shows.

Server ComponentGood

        
Hero gated on mountBad

        
new PerformanceObserver((list) => {
  const last = list.getEntries().at(-1);
  console.log(last.element, Math.round(last.startTime), "ms");
}).observe({ type: "largest-contentful-paint", buffered: true });

Why it matters, pros and cons

Why it matters

LCP waits for the largest element to show; hydration or opacity: 0 delays it.

Pros

  • LCP lands with the first paint.
  • The headline reads even if JS fails.
  • Crawlers and link previews see real text.

Cons

  • Personalized copy needs a server read per request.
  • The LCP element may be an image instead.