Server Side Rendering
Improve time-to-first-byte and perceived performance with server-side rendering, static generation, and streaming patterns.
You are an expert in SSR and SSG performance patterns for optimizing application performance. ## Key Points - **CSR:** Browser downloads a minimal HTML shell, then JS fetches data and renders the UI. Fast subsequent navigations but slow initial load. - **SSR:** Server renders the full HTML per request. Fast FCP but adds server compute cost and TTFB latency. - **SSG:** Pages are pre-built as static HTML at deploy time. Fastest TTFB possible (served from CDN) but only works for content that does not change per request. - **ISR (Incremental Static Regeneration):** A hybrid — serves static pages but re-generates them in the background after a configured interval. - **Streaming SSR:** The server sends HTML in chunks as components resolve, allowing the browser to start rendering before the full response is complete. - **Hydration:** The process of attaching JavaScript event handlers to server-rendered HTML. Until hydration completes, the page is visible but not interactive. - **TTFB:** The primary metric to watch. SSG should deliver < 100ms TTFB from a CDN. SSR typically ranges 200-800ms depending on data fetching. - **FCP vs TTI gap:** A large gap indicates slow hydration. Consider partial hydration or React Server Components to reduce client JS. - **Server compute cost:** Monitor SSR function execution time and memory usage. Cache aggressively with `stale-while-revalidate` to reduce origin hits. - **Hydration timing:** Measure using `performance.mark` before and after `hydrateRoot` to track hydration duration. - Default to SSG/ISR for content that does not change per user. Reserve SSR for personalized or real-time pages. - Use streaming SSR with `Suspense` boundaries so fast content is visible immediately while slow data loads in the background.
skilldb get performance-optimization-skills/Server Side RenderingFull skill: 130 linesServer-Side Rendering — Performance Optimization
You are an expert in SSR and SSG performance patterns for optimizing application performance.
Core Philosophy
Overview
Server-Side Rendering (SSR) generates HTML on the server for each request, delivering a fully rendered page to the browser. Static Site Generation (SSG) pre-renders pages at build time. Both approaches dramatically improve First Contentful Paint (FCP) and LCP compared to client-side rendering (CSR), where users see a blank page until JavaScript downloads and executes.
Core Concepts
- CSR: Browser downloads a minimal HTML shell, then JS fetches data and renders the UI. Fast subsequent navigations but slow initial load.
- SSR: Server renders the full HTML per request. Fast FCP but adds server compute cost and TTFB latency.
- SSG: Pages are pre-built as static HTML at deploy time. Fastest TTFB possible (served from CDN) but only works for content that does not change per request.
- ISR (Incremental Static Regeneration): A hybrid — serves static pages but re-generates them in the background after a configured interval.
- Streaming SSR: The server sends HTML in chunks as components resolve, allowing the browser to start rendering before the full response is complete.
- Hydration: The process of attaching JavaScript event handlers to server-rendered HTML. Until hydration completes, the page is visible but not interactive.
Implementation Patterns
Next.js — Static Generation with Fallback
// pages/posts/[slug].js — SSG with ISR
export async function getStaticProps({ params }) {
const post = await fetchPost(params.slug);
return {
props: { post },
revalidate: 3600, // regenerate every hour
};
}
export async function getStaticPaths() {
const slugs = await fetchPopularSlugs();
return {
paths: slugs.map(slug => ({ params: { slug } })),
fallback: 'blocking', // SSR on first request for unknown slugs
};
}
React Server Components (App Router)
// app/dashboard/page.tsx — server component by default
export default async function DashboardPage() {
const data = await db.query('SELECT * FROM metrics');
return (
<div>
<h1>Dashboard</h1>
<MetricsTable data={data} />
{/* Client component for interactive parts only */}
<Suspense fallback={<Skeleton />}>
<InteractiveChart />
</Suspense>
</div>
);
}
Streaming SSR with Suspense
Before (blocking SSR):
// Entire page waits for slowest data fetch
export async function getServerSideProps() {
const [user, posts, recommendations] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchRecommendations(), // slow — 800ms
]);
return { props: { user, posts, recommendations } };
}
After (streaming):
// app/page.tsx — recommendations stream in when ready
export default async function Page() {
const [user, posts] = await Promise.all([fetchUser(), fetchPosts()]);
return (
<div>
<Header user={user} />
<PostList posts={posts} />
<Suspense fallback={<RecommendationsSkeleton />}>
<Recommendations /> {/* fetches its own data, streams when ready */}
</Suspense>
</div>
);
}
Measurement & Monitoring
- TTFB: The primary metric to watch. SSG should deliver < 100ms TTFB from a CDN. SSR typically ranges 200-800ms depending on data fetching.
- FCP vs TTI gap: A large gap indicates slow hydration. Consider partial hydration or React Server Components to reduce client JS.
- Server compute cost: Monitor SSR function execution time and memory usage. Cache aggressively with
stale-while-revalidateto reduce origin hits. - Hydration timing: Measure using
performance.markbefore and afterhydrateRootto track hydration duration.
Best Practices
- Default to SSG/ISR for content that does not change per user. Reserve SSR for personalized or real-time pages.
- Use streaming SSR with
Suspenseboundaries so fast content is visible immediately while slow data loads in the background. - Minimize the amount of JavaScript that needs to hydrate by using Server Components for data-fetching and static UI.
Common Pitfalls
- Hydration mismatch errors caused by rendering different content on server vs client (e.g., using
Date.now()orwindowduring SSR), which forces React to discard the server HTML and re-render from scratch. - Running expensive database queries directly in SSR without caching, turning every page view into a full database round-trip and degrading TTFB under load.
Anti-Patterns
Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.
Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.
Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.
Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.
Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.
Install this skill directly: skilldb add performance-optimization-skills
Related Skills
Bundle Optimization
Reduce JavaScript bundle size through tree shaking, dependency analysis, and build configuration tuning.
Caching Strategies
Design effective browser and CDN caching strategies using Cache-Control headers, ETags, service workers, and edge caching.
Code Splitting
Reduce initial load time with route-based and component-level code splitting using dynamic imports and framework-specific patterns.
Core Web Vitals
Optimize Largest Contentful Paint, First Input Delay, and Cumulative Layout Shift for better user experience and search rankings.
Database Query Optimization
Optimize database queries through indexing, query planning, N+1 elimination, connection pooling, and schema design.
Image Optimization
Optimize image delivery with modern formats, responsive sizing, lazy loading, and CDN-based transformations.