Fix Client-Side Rendering for Better LCP
When content only appears after JavaScript runs, LCP is delayed until the JS downloads, parses, executes, and renders. This can add seconds to LCP.
What's the Problem?
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:
- Download HTML shell (fast)
- Download JavaScript bundle (slow)
- Parse and execute JavaScript (slow)
- Fetch data from API (slow)
- Render content (finally, LCP fires)
With server-side rendering:
- Download HTML with content already included (LCP fires)
How to Identify This Issue
View Page Source
- Right-click → "View Page Source"
- Search for your main content text
- If it's not there, you're using client-side rendering
Lighthouse
Look for:
- High "Total Blocking Time"
- Large "JavaScript execution time"
- LCP element rendered by JavaScript (shown in audit details)
Chrome DevTools
- Disable JavaScript: Settings → Debugger → Disable JavaScript
- Reload the page
- If your content disappears, it's client-side rendered
The Fix
1. Use Server-Side Rendering (SSR)
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>
2. Use Static Site Generation (SSG)
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 }
}
})
3. Streaming SSR
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>
</>
)
}
4. Hybrid Approach
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 />
</>
)
}
5. Inline Critical Data
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>
Framework-Specific Solutions
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.Verify the Fix
After implementing:
- View Page Source — content should be visible
- Disable JavaScript — content should still appear
- Run Lighthouse — LCP should improve significantly
Expected improvement: Moving from CSR to SSR can improve LCP by 1-3 seconds.
Common Mistakes
- Hydration mismatch — Server and client must render the same content. Mismatches cause re-renders and can hurt LCP.
- Blocking on non-critical data — Only wait for data needed for LCP. Load other data after.
- Large server bundles — SSR with huge dependencies increases TTFB. Keep server code lean.
Trade-offs
| 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 |
Related Issues
Often appears alongside:
- Slow Server Response — SSR adds server processing time
- Large Images — Still need to optimize images with SSR
Test Your Entire Site
Some pages may use CSR while others use SSR. Unlighthouse scans your entire site and identifies pages with client-side rendering issues.