Learnings to Myself - 3

May 14, 2024

15 min

A Note from My Learning Journey

Hey there! This post is part of my “learnings to myself” series, a place where I write down what I’m learning, struggling with, or trying to understand better as I grow in the world of development.

I’ve always found it helpful to take notes as I learn, and now I’m turning some of those notes into blog posts. Not because I’m an expert, quite the opposite, actually :), but because writing helps me understand things better, and maybe, just maybe, someone else might find it helpful too.

Quick disclaimer in case it’s needed:
I’m not an academic or an authority on these topics. My goal isn’t to teach like a textbook, but to document my process, share my thoughts, and leave a small digital footprint in a field I really enjoy learning about.

Rendering Strategies in Next.js

One of the biggest things I’ve wrestled with while using Next.js is this:

  • Should this page be rendered on the server or the client?
  • Should it be static or dynamic?
  • Is ISR better here, or do I need SSR?

What does this even mean? 🤔 What's going oooon? 🎤🎶

Core Question: Where is the content rendered?

Next.js gives us 5 main strategies to render content:

  • CSR - Client-Side Rendering
  • SSR - Server-Side Rendering
  • SSG - Static Site Generation
  • ISR - Incremental Static Regeneration
  • RSC/PPR - React Server Components / Partial Prerendering

What is Server-Side Rendering (SSR)?

Server-side rendering means that the HTML of your page is generated on the server for every single request. When someone visits your site, your server: Fetches the data. Renders the HTML, Sends it to the browser, fully formed and ready to display.

export default function Page({ data }) {
  // Render data...
}
 
// This gets called on every request
export async function getServerSideProps() {
  // Fetch data from external API
  const res = await fetch(`https://.../data`)
  const data = await res.json()
 
  // Pass data to the page via props
  return { props: { data } }
}

  • User requests a page.
  • getServerSideProps runs on the server to fetch fresh data.
  • The server generates the full HTML with the data.
  • The browser receives and immediately displays the rendered HTML.
  • JavaScript is loaded and hydration occurs to enable interactivity.

This feature allows for faster indexing in search engine results.

Pros

  • Great for SEO
  • Fast first load
  • Fresh content on every visit

Cons

  • Increased server load
  • Latency vs static pages
  • Requires hydration

Use when

  • SEO matters and data changes frequently
  • like user dashboards or product detail pages


What is Client-Side Rendering (CSR)?

Client-side rendering means that the server sends a minimal HTML shell, and the browser takes over from there. JavaScript runs in the user's browser, fetches data, and builds the page dynamically.

Typically, this happens in components using React hooks like useEffect.

import React, { useState, useEffect } from 'react'
 
export function Page() {
  const [data, setData] = useState(null)
 
  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch('https://api.example.com/data')
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`)
      }
      const result = await response.json()
      setData(result)
    }
 
    fetchData().catch((e) => {
      // handle the error as needed
      console.error('An error occurred while fetching the data: ', e)
    })
  }, [])
 
  return <p>{data ? `Your data: ${data}` : 'Loading...'}</p>
}

Pros

  • Lighter server load – Rendering happens in the browser
  • Interactive UIs – Ideal for apps with lots of client-side logic or user interactions.

Cons

  • Slower first load – Content appears only after JavaScript runs and fetches data.
  • SEO limitations – Search engines can crawl JavaScript, but it’s still not as reliable as SSR.
  • Initial blank screen (waiting for JS to load and run)


When Should You Use SSR or CSR?

Use SSR when:

  • You need real-time, dynamic data.
  • SEO is a priority.
  • You want content to appear as fast as possible on first load.

Use CSR when:

  • The page doesn’t need to be indexed by search engines.
  • You’re building an internal tool, dashboard, or admin panel.
  • Interactivity and responsiveness are more important than initial load speed.

What About SSG and ISR?

Traditional Server-Side Rendering (SSR) websites had a major drawback: every user request triggered the server to render the page from scratch. This caused significant load on the server, especially as traffic increased. To address this issue, instead of rendering pages on every request, SSG generates all necessary HTML files at build time, only once. These static files are then served directly to users without any need for server-side rendering at request time. This approach drastically reduces server load and improves performance.

Over time, SSG evolved into more advanced frameworks like Gatsby.js, which brought this concept into the React ecosystem. While SSG is extremely fast and efficient, it has a limitation: it’s not ideal for dynamic content. Since pages are generated at build time, any changes in the database won't be reflected on the site until the next rebuild. This means users may see stale content, and developers are forced to manually trigger rebuilds for every data change.

As a result, SSG is best suited for projects with relatively static content — like blogs, documentation, or digital menus. To overcome this limitation, Incremental Static Regeneration (ISR) was introduced. ISR allows statically generated pages to be re-rendered at regular intervals (e.g., every 30 minutes), in the background. The next user who visits the page after the regeneration interval receives the updated content. This hybrid approach ensures that users get near real-time updates while still benefiting from the performance of static pages. With ISR, developers can build fast, scalable, and partially dynamic web applications without overloading the server or sacrificing content freshness.

SSG (Static Site Generation): Pages are generated at build time and served as static HTML from a CDN. Super fast and great for content that rarely changes. This blog page is example of SSG. 🎯

When you use Static Site Generation (SSG) in Next.js, the page is pre-rendered at build time. In practice, this means that when you push your code to GitHub, Vercel runs npm run build (which is just next build under the hood), generates the static HTML, and deploys it to the edge. it all happens automatically during deployment.

For example in this website I wrote this code:

export async function generateStaticParams() {
  let posts = getBlogPosts();
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

  • This runs at build time
  • 📁 It’s used inside a route like /app/blog/[slug]/
  • 📌 Think of it as: “Here are all the blog pages I want to generate statically.”
  • generateStaticParams() can read data from the file system (like .md files) or from an external API. Since it's executed at build time, any data it fetches is used to generate static HTML pages ahead of time.

You should ask yourself: "Can I pre-render this page ahead of a user's request?" If the answer is yes, then you should choose Static Generation.

ISR (Incremental Static Regeneration): Like SSG but with automatic revalidation. Pre-generated pages are updated at specific intervals. Allows updating static pages on-demand after deployment — a balance between static speed and dynamic flexibility.

export async function getStaticProps() {
  return {
    props: { ... },
    revalidate: 3600, // Rebuild every hour
  };
}

OR You can use at the top the page:

export const relivadate = 3600 // invalidate every hour

OR revalidate the request after some time

export default async function Home() {
  const response = await fetch("https://api.vercel.app/blog", {
	  next: {revalidate: 3600}
  });
	 
  const posts = await response.json();
  return ( ... )

React Server Components & Partial Prerendering (RSC/PPR)

This lets you render components on the server without sending unnecessary JS to the client. You can fetch data using async/await directly in components Reduces client-side JS, boosts performance Partial Prerendering (PPR) allows a hybrid of static + dynamic rendering on the same page.


export default async function Page() {
  const data = await fetchData();
  return <UI data={data} />;
}


Rendering StrategyWhen RenderedInitial Load SpeedSEOPersonalized ContentServer LoadBest Use Case
CSROn clientSlowWeakLowAdmin panels, interactive dashboards
SSROn each requestMediumStrongHighUser dashboards, real-time content
SSGAt build timeVery fastStrongNoneBlogs, documentation, landing pages
ISRBuild + revalidateFastStrongLowBlog posts, product listings
RSC / PPRServer + streamFastStrongMediumLarge-scale apps, hybrid pages

What is Streaming?

One last concept that really helped me understand modern rendering is: Streaming.

Streaming is a way to break a page into smaller chunks and send them to the browser as soon as they're ready — without waiting for the whole page to load. It’s super helpful when:

  • You have a slow API request that shouldn’t block the entire UI
  • Some parts of the page (like a sidebar) are static, but other parts (like dynamic charts or user data) take time to fetch

Here are two ways you implement streaming:

  1. At the page level, using loading.tsx

If you create a loading.tsx file next to your page (like /app/dashboard/loading.tsx), Next.js will automatically show that fallback UI while the page’s server components are being prepared.

Example:

📁 /app/dashboard/loading.tsx

export default function Loading() {
  return <p>Preparing your dashboard...</p>;
}

📁 /app/dashboard/page.tsx

export default async function DashboardPage() {
  const res = await fetch("https://api.example.com/dashboard");
  const data = await res.json();

  return (
    <div>
      <h1>Dashboard</h1>
      <p>Welcome back, {data.username}!</p>
    </div>
  );
}

This allows you to:

  • Show static UI (like a sidebar) instantly
  • Delay dynamic parts while keeping the page responsive
  • Avoid blocking navigation – users can leave before data is done loading (interruptible navigation)

  1. At the component level, using Suspense

If you want more granular control (e.g., stream just a specific section of the page), you can use React’s Suspense directly.

Example:

📁 /components/UserStats.tsx

async function getUserStats() {
  const res = await fetch('https://api.example.com/user-stats');
  return res.json();
}

export default async function UserStats() {
  const stats = await getUserStats();
  return <div>Points: {stats.points}</div>;
}

📁 /app/dashboard/page.tsx

import { Suspense } from "react";
import UserStats from "@/components/UserStats";

export default function DashboardPage() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Suspense fallback={<p>Loading user stats...</p>}>
        <UserStats />
      </Suspense>
    </div>
  );
}

For example, in this website; since SideNav is static, it's shown immediately. The user can interact with SideNav while the dynamic content is loading.

Final Thoughts

For me, digging into this topic helped make a few blurry areas a lot clearer, and if you made it this far, I hope it did the same for you.

Thanks for reading and rendering!