Ship the hero in server HTML
Put the hero headline in server HTML; never fade it in from opacity: 0.
From nextjs-pagespeed.
Three ways to ship it
Press Reload, then drag the hydration delay.
A · In the HTMLGood
B · After hydrationBad
C · HTML, fades from 0Bad
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.