Back to blog
Nov 21, 2025
7 min read

ISR vs SSR vs CSR: The Complete Guide to Rendering Strategies

Master the art of choosing the right rendering strategy for your web applications. Learn when to use ISR, SSR, CSR, and SSG with practical examples.

I’ve built web apps using every rendering strategy out there. Here’s what I learned about when to use each one.

The Problem with Wrong Rendering Choices

Last year, I built an e-commerce site using pure CSR. Big mistake. Google couldn’t index product pages, SEO was terrible, and initial load times were 3+ seconds. I had to rebuild the entire thing with SSR.

Choosing the wrong rendering strategy costs time, money, and users.

Client-Side Rendering (CSR)

What it is: JavaScript renders everything in the browser after downloading.

When I use it:

  • Admin dashboards
  • Internal tools
  • Apps behind authentication
  • Heavy interactive features

Real Implementation

// Dashboard with real-time data
function AdminDashboard() {
  const [metrics, setMetrics] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // Fetch data after component mounts
    fetchDashboardData()
      .then(data => {
        setMetrics(data);
        setLoading(false);
      });
      
    // Set up real-time updates
    const interval = setInterval(fetchDashboardData, 30000);
    return () => clearInterval(interval);
  }, []);
  
  if (loading) return <DashboardSkeleton />;
  
  return (
    <div>
      <MetricsCards data={metrics} />
      <RealtimeChart data={metrics.chartData} />
      <UserTable users={metrics.users} />
    </div>
  );
}

Pros:

  • Fast navigation between pages
  • Rich interactions and animations
  • Reduced server load
  • Great for SPAs

Cons:

  • Poor SEO (search engines struggle)
  • Slow initial load
  • Requires JavaScript to work
  • Large bundle sizes

Performance: 2-4 second initial load, then instant navigation.

Server-Side Rendering (SSR)

What it is: Server generates HTML for each request, sends complete page to browser.

When I use it:

  • E-commerce product pages
  • Blog posts
  • Marketing landing pages
  • Anything that needs SEO

Real Implementation

// Next.js product page
export async function getServerSideProps({ params }) {
  // This runs on every request
  const product = await fetchProduct(params.id);
  const reviews = await fetchReviews(params.id);
  const relatedProducts = await fetchRelatedProducts(product.category);
  
  if (!product) {
    return { notFound: true };
  }
  
  return {
    props: {
      product,
      reviews,
      relatedProducts
    }
  };
}

function ProductPage({ product, reviews, relatedProducts }) {
  return (
    <div>
      <ProductDetails product={product} />
      <ReviewsList reviews={reviews} />
      <RelatedProducts products={relatedProducts} />
    </div>
  );
}

Pros:

  • Excellent SEO
  • Fast initial page load
  • Works without JavaScript
  • Good for dynamic content

Cons:

  • Slower navigation
  • Higher server costs
  • Increased TTFB (Time to First Byte)
  • Server needs to handle all requests

Performance: 500ms-1s initial load, 300-800ms navigation.

Static Site Generation (SSG)

What it is: Pages are pre-built at build time, served as static files.

When I use it:

  • Documentation sites
  • Portfolios
  • Landing pages
  • Content that doesn’t change often

Real Implementation

// Blog post page
export async function getStaticProps({ params }) {
  // This runs at build time
  const post = await getPostBySlug(params.slug);
  const relatedPosts = await getRelatedPosts(post.tags);
  
  return {
    props: {
      post,
      relatedPosts
    },
    // Regenerate page every 24 hours
    revalidate: 86400
  };
}

export async function getStaticPaths() {
  // Pre-generate paths for popular posts
  const popularPosts = await getPopularPosts(100);
  
  return {
    paths: popularPosts.map(post => ({
      params: { slug: post.slug }
    })),
    // Generate other pages on-demand
    fallback: 'blocking'
  };
}

function BlogPost({ post, relatedPosts }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
      <RelatedPosts posts={relatedPosts} />
    </article>
  );
}

Pros:

  • Lightning fast (served from CDN)
  • Excellent SEO
  • Cheap hosting
  • Great for content sites

Cons:

  • Build times increase with more pages
  • Not suitable for user-specific content
  • Changes require rebuilds

Performance: 100-300ms load time.

Incremental Static Regeneration (ISR)

What it is: Static pages that can be updated in the background without full rebuilds.

When I use it:

  • News websites
  • Product catalogs
  • Content that updates regularly
  • High-traffic sites with dynamic data

Real Implementation

// News article page
export async function getStaticProps({ params }) {
  const article = await fetchArticle(params.slug);
  const comments = await fetchComments(params.slug);
  
  return {
    props: {
      article,
      comments,
      lastUpdated: new Date().toISOString()
    },
    // Regenerate every 60 seconds if there's traffic
    revalidate: 60
  };
}

export async function getStaticPaths() {
  // Only pre-generate recent articles
  const recentArticles = await getRecentArticles(50);
  
  return {
    paths: recentArticles.map(article => ({
      params: { slug: article.slug }
    })),
    // Generate older articles on first request
    fallback: 'blocking'
  };
}

function NewsArticle({ article, comments, lastUpdated }) {
  return (
    <div>
      <article>
        <h1>{article.title}</h1>
        <time>{article.publishedAt}</time>
        <div dangerouslySetInnerHTML={{ __html: article.content }} />
      </article>
      
      <CommentsSection comments={comments} />
      
      <small>Last updated: {lastUpdated}</small>
    </div>
  );
}

Pros:

  • Fast like SSG
  • Fresh content
  • Scales well
  • Good SEO

Cons:

  • Complex caching behavior
  • Potential for stale content
  • Harder to debug

Performance: 150-400ms (cached), 800ms-1.5s (regenerating).

My Decision Framework

Here’s how I choose:

1. Does it need SEO?

  • Yes: SSR or SSG/ISR
  • No: CSR is fine

2. How often does content change?

  • Never/Rarely: SSG
  • Hourly/Daily: ISR
  • Real-time: SSR or CSR

3. Is it user-specific?

  • Yes: CSR or SSR
  • No: SSG or ISR

4. What’s the traffic like?

  • High: SSG/ISR (CDN benefits)
  • Low: SSR is fine

Real Project Examples

E-commerce Site

  • Homepage: SSG (rarely changes)
  • Product pages: ISR (inventory updates)
  • User dashboard: CSR (personal data)
  • Checkout: SSR (SEO + security)

News Website

  • Article pages: ISR (content updates)
  • Homepage: ISR (new articles)
  • Admin panel: CSR (internal tool)
  • Author pages: SSG (static bios)

SaaS Application

  • Landing page: SSG (marketing)
  • Documentation: SSG (static content)
  • App dashboard: CSR (interactive)
  • Pricing page: SSG (rarely changes)

Hybrid Implementation

// Different strategies in one Next.js app

// pages/index.js - SSG for landing
export async function getStaticProps() {
  return { props: { hero: await getHeroContent() } };
}

// pages/blog/[slug].js - ISR for articles
export async function getStaticProps() {
  return { 
    props: { post: await getPost() },
    revalidate: 3600 // 1 hour
  };
}

// pages/dashboard/[...slug].js - CSR for app
function Dashboard() {
  // Client-side data fetching
  const { data } = useSWR('/api/dashboard', fetcher);
  return <DashboardUI data={data} />;
}

// pages/product/[id].js - SSR for SEO
export async function getServerSideProps() {
  return { props: { product: await getProduct() } };
}

Performance Monitoring

I track these metrics for each strategy:

// Core Web Vitals tracking
function trackPerformance(strategy) {
  // Largest Contentful Paint
  new PerformanceObserver((list) => {
    const lcp = list.getEntries()[0];
    analytics.track('LCP', {
      value: lcp.startTime,
      strategy: strategy
    });
  }).observe({ entryTypes: ['largest-contentful-paint'] });
  
  // First Input Delay
  new PerformanceObserver((list) => {
    const fid = list.getEntries()[0];
    analytics.track('FID', {
      value: fid.processingStart - fid.startTime,
      strategy: strategy
    });
  }).observe({ entryTypes: ['first-input'] });
}

Common Mistakes I’ve Made

  1. Using SSR for everything - Killed server performance
  2. Pure CSR for content sites - SEO disaster
  3. ISR without proper cache headers - Confused users with stale content
  4. SSG for user-specific data - Security nightmare

Tools I Use

  • Next.js: Best framework for mixing strategies
  • Vercel Analytics: Performance monitoring
  • Lighthouse CI: Automated performance testing
  • React Query: Client-side data management

The Bottom Line

There’s no perfect rendering strategy. I usually end up with:

  • Marketing pages: SSG
  • Content pages: ISR
  • User dashboards: CSR
  • Critical SEO pages: SSR

Start with what you know, measure performance, and optimize based on real user data. Don’t over-engineer from day one.