When content only appears after JavaScript runs, LCP is delayed until the JS downloads, parses, executes, and renders. This can add seconds to LCP.
Client-side rendering (CSR) means the server sends a minimal HTML shell, and JavaScript builds the actual content in the browser.
The LCP timeline with CSR:
With server-side rendering:
Look for:
Render HTML on the server so content is available immediately.
// Next.js - automatic SSR with App Router
export default async function Page() {
const data = await fetchData() // Runs on server
return <Content data={data} /> // HTML sent to browser
}
<!-- Nuxt - automatic SSR -->
<script setup>
const { data } = await useFetch('/api/content')
</script>
<template>
<Content :data="data" />
</template>
Pre-render pages at build time for the fastest possible LCP.
// Next.js - static generation
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map(post => ({ slug: post.slug }))
}
export default async function Page({ params }) {
const post = await getPost(params.slug)
return <Article post={post} />
}
// Nuxt - static generation
export default defineNuxtConfig({
routeRules: {
'/blog/**': { prerender: true }
}
})
Send HTML progressively so users see content before the entire page is ready.
// Next.js App Router - automatic streaming
import { Suspense } from 'react'
export default function Page() {
return (
<>
<Header />
{' '}
{/* Sent immediately */}
<HeroImage />
{' '}
{/* LCP element - sent immediately */}
<Suspense fallback={<Loading />}>
<SlowContent />
{' '}
{/* Streamed later */}
</Suspense>
</>
)
}
If you must use CSR for some parts, ensure the LCP element is in the initial HTML.
// LCP element server-rendered, interactive parts client-rendered
export default function Page() {
return (
<>
{/* Server-rendered, appears immediately */}
<img src="/hero.jpg" alt="Hero" />
<h1>Page Title</h1>
{/* Client-rendered, but not LCP */}
<InteractiveWidget />
</>
)
}
If you need data for the LCP element, inline it in the HTML.
<script id="initial-data" type="application/json">
{"hero": {"title": "Welcome", "image": "/hero.jpg"}}
</script>
<script>
// No network request needed for initial render
const data = JSON.parse(document.getElementById('initial-data').textContent)
</script>
getServerSideProps/getStaticProps. Never use useEffect for LCP content.useFetch or useAsyncData (not $fetch in onMounted) for data that affects LCP.react-snap.vite-plugin-ssr or pre-render with vite-ssg.After implementing:
Expected improvement: Moving from CSR to SSR can improve LCP by 1-3 seconds.
| Approach | LCP | Interactivity | Complexity |
|---|---|---|---|
| CSR | Poor | Fast after load | Simple |
| SSR | Good | Delayed (hydration) | Medium |
| SSG | Best | Delayed (hydration) | Medium |
| Streaming SSR | Good | Progressive | Higher |
Often appears alongside:
Some pages may use CSR while others use SSR. Unlighthouse scans your entire site and identifies pages with client-side rendering issues.