<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged ssg</title>
<link>https://coderlegion.com/tag/ssg</link>
<description>Powered by Question2Answer</description>
<item>
<title>Next.js Rendering Strategies Explained: SSR, SSG, CSR, ISR &amp; Cache Components (2026)</title>
<link>https://coderlegion.com/23657/next-js-rendering-strategies-explained-ssr-ssg-csr-isr-cache-components-2026</link>
<description>&lt;p&gt;Ever opened the Next.js docs and seen acronyms like SSR, SSG, CSR, and ISR thrown around like everyone's supposed to just &lt;em&gt;know&lt;/em&gt; what they mean? You're not alone. Half the confusion around Next.js performance comes down to one simple question nobody explains clearly enough: &lt;strong&gt;when does your page actually get built — and where?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Once you understand that one question, every rendering strategy in Next.js suddenly makes sense. By the end of this guide, you'll know exactly what SSR, SSG, CSR, and ISR mean, how the newer &lt;strong&gt;Cache Components&lt;/strong&gt; model (powered by &lt;strong&gt;Partial Prerendering&lt;/strong&gt;, or PPR) ties it all together, and which one to reach for in your own projects.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-matters&quot;&gt;Why This Matters&lt;/h3&gt;
&lt;p&gt;Every page you build has to answer one question: &lt;strong&gt;&quot;When should the HTML for this page get generated?&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There are really only three possible answers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Ahead of time&lt;/strong&gt;, during &lt;code&gt;next build&lt;/code&gt; — before any user even visits the site.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;On request&lt;/strong&gt;, every time a user visits — generated fresh on the server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In the browser&lt;/strong&gt;, after the page loads — generated by JavaScript running on the user's device.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Next.js gives you a strategy for each answer, plus a smart hybrid (Cache Components) that mixes them on the &lt;em&gt;same page&lt;/em&gt;. Here's the whole picture at a glance before we go through each one:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://devstacked.tech/nextjs-rendering-strategies-explained/image1.svg&quot; alt=&quot;Diagram comparing when SSR, SSG, CSR, ISR, and Cache Components render — across build time, request time, and the browser&quot;&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-ssr-server-side-rendering-&quot;&gt;What Is SSR (Server-Side Rendering)?&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Think of it like:&lt;/strong&gt; ordering food at a restaurant — you ask, the kitchen cooks it fresh, then it's served to you. Every single time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;SSR means the HTML for a page is generated &lt;strong&gt;on the server, for every single request&lt;/strong&gt;. The user always gets fresh data, but the server has to do work on every visit.&lt;/p&gt;
&lt;p&gt;In the Next.js App Router, you get SSR automatically whenever a page reads something that can only be known at request time — like cookies, headers, or search params — or when you explicitly opt a page into dynamic rendering.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/dashboard/page.tsx
import { cookies } from &quot;next/headers&quot;;

export default async function DashboardPage() {
  const cookieStore = await cookies();
  const userId = cookieStore.get(&quot;userId&quot;)?.value;

  const res = await fetch(`https://api.example.com/users/${userId}`, {
    cache: &quot;no-store&quot;, // tells Next.js: never cache this, always fetch fresh
  });
  const user = await res.json();

  return &amp;lt;h1&amp;gt;Welcome back, {user.name}&amp;lt;/h1&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What's happening here:&lt;/strong&gt; because we read &lt;code&gt;cookies()&lt;/code&gt; and pass &lt;code&gt;cache: &quot;no-store&quot;&lt;/code&gt; to &lt;code&gt;fetch&lt;/code&gt;, Next.js knows this page depends on request-specific data. It renders the page on the server every time someone visits, instead of reusing a cached copy.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use SSR when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The content is personalized per user (dashboards, account pages).&lt;/li&gt;
&lt;li&gt;Data changes frequently and must always be fresh (stock prices, live order status).&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Using SSR for content that rarely changes (like a blog post) wastes server resources. If the data isn't user-specific or constantly changing, you probably want SSG or ISR instead.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-ssg-static-site-generation-&quot;&gt;What Is SSG (Static Site Generation)?&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Think of it like:&lt;/strong&gt; a printed menu — it's made once, in advance, and handed to every customer exactly the same.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;SSG means the HTML is generated &lt;strong&gt;once, at build time&lt;/strong&gt;, and then reused for every visitor. No server work happens per request — Next.js just serves the same pre-built HTML file.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/blog/[slug]/page.tsx
import { getPostBySlug, getAllPosts } from &quot;@/lib/posts&quot;;

// This tells Next.js which pages to pre-build at build time
export async function generateStaticParams() {
  const posts = getAllPosts();
  return posts.map((post) =&amp;gt; ({ slug: post.slug }));
}

export default async function BlogPostPage({
  params,
}: {
  params: Promise&amp;lt;{ slug: string }&amp;gt;;
}) {
  const { slug } = await params;
  const post = getPostBySlug(slug);

  return (
    &amp;lt;article&amp;gt;
      &amp;lt;h1&amp;gt;{post.title}&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;{post.description}&amp;lt;/p&amp;gt;
    &amp;lt;/article&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What's happening here:&lt;/strong&gt; &lt;code&gt;generateStaticParams()&lt;/code&gt; tells Next.js exactly which blog post pages exist. At build time, Next.js renders each one into static HTML. When a real visitor requests &lt;code&gt;/blog/my-post&lt;/code&gt;, the server just hands over the already-built file — no rendering work, no database hit, nothing. It's instant.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use SSG when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Content doesn't change often (blog posts, marketing pages, documentation).&lt;/li&gt;
&lt;li&gt;You want the fastest possible load times and lowest server cost.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; If your content updates frequently but you're using pure SSG, visitors will keep seeing stale data until you rebuild and redeploy the entire site. That's exactly the problem ISR solves — keep reading.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-csr-client-side-rendering-&quot;&gt;What Is CSR (Client-Side Rendering)?&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Think of it like:&lt;/strong&gt; ordering a build-it-yourself meal kit — the server hands you the box and instructions (a minimal HTML shell), but your browser does the actual cooking (fetching data and rendering the final content).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A common misconception is that CSR means the server does nothing. It doesn't — Next.js still renders and sends a minimal HTML shell from the server for Client Components (usually the loading/empty state). What makes it &quot;client-side&quot; is that the &lt;strong&gt;data fetching and the final content render&lt;/strong&gt; happen in the browser, after JavaScript downloads and runs. The browser then &quot;hydrates&quot; that HTML — attaching event listeners and making it interactive — before fetching data and re-rendering with the real content.&lt;/p&gt;
&lt;p&gt;In Next.js, you get CSR by using Client Components with hooks like &lt;code&gt;useState&lt;/code&gt; and &lt;code&gt;useEffect&lt;/code&gt; (or a data-fetching library like React Query).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// components/live-notifications.tsx
&quot;use client&quot;;

import { useEffect, useState } from &quot;react&quot;;

type Notification = { id: string; message: string };

export function LiveNotifications() {
  const [notifications, setNotifications] = useState&amp;lt;Notification[]&amp;gt;([]);

  useEffect(() =&amp;gt; {
    fetch(&quot;/api/notifications&quot;)
      .then((res) =&amp;gt; res.json())
      .then(setNotifications);
  }, []);

  return (
    &amp;lt;ul&amp;gt;
      {notifications.map((n) =&amp;gt; (
        &amp;lt;li key={n.id}&amp;gt;{n.message}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What's happening here:&lt;/strong&gt; the &lt;code&gt;&quot;use client&quot;&lt;/code&gt; directive tells Next.js this component should be interactive in the browser. The server sends an initial HTML shell (which may contain little or no real data), then the browser fetches data and renders the final UI after hydration. Once JavaScript loads and hydrates that HTML, &lt;code&gt;useEffect&lt;/code&gt; fires, fetches data from &lt;code&gt;/api/notifications&lt;/code&gt;, and re-renders the list with real content — all in the browser.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use CSR when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The content is highly interactive and user-specific (notification feeds, live chat, dashboards that update constantly).&lt;/li&gt;
&lt;li&gt;SEO doesn't matter for that specific piece of UI (search engines don't need to see it).&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Making your &lt;em&gt;entire&lt;/em&gt; page a Client Component &quot;just to be safe.&quot; This kills SEO and slows down the initial load. Only mark the interactive parts as client components — let everything else stay on the server.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-isr-incremental-static-regeneration-&quot;&gt;What Is ISR (Incremental Static Regeneration)?&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Think of it like:&lt;/strong&gt; a printed menu that quietly reprints itself in the background every hour — customers always get &lt;em&gt;a&lt;/em&gt; menu instantly, and it's rarely more than an hour out of date.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ISR is SSG's smarter sibling. Pages are still pre-built and served instantly like static pages, but Next.js automatically &lt;strong&gt;regenerates them in the background&lt;/strong&gt; after a set amount of time, without you needing to rebuild and redeploy your whole app.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/products/[id]/page.tsx
async function getProduct(id: string) {
  const res = await fetch(`https://api.example.com/products/${id}`, {
    next: { revalidate: 3600 }, // regenerate this page at most once every hour
  });
  return res.json();
}

export default async function ProductPage({
  params,
}: {
  params: Promise&amp;lt;{ id: string }&amp;gt;;
}) {
  const { id } = await params;
  const product = await getProduct(id);

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{product.name}&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;${product.price}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What's happening here:&lt;/strong&gt; &lt;code&gt;revalidate: 3600&lt;/code&gt; tells Next.js &quot;this page can be served from cache for up to 3,600 seconds (1 hour).&quot; The first visitor after that hour expires still gets the &lt;em&gt;old&lt;/em&gt; cached page instantly — but Next.js triggers a regeneration in the background, so the &lt;em&gt;next&lt;/em&gt; visitor gets the updated version. Nobody ever waits for a rebuild.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Use ISR when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Content changes occasionally, but not so often that it needs to be SSR (product pages, pricing pages, news listings).&lt;/li&gt;
&lt;li&gt;You want the speed of static pages without manually rebuilding every time data changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-are-cache-components-and-ppr-&quot;&gt;What Are Cache Components (and PPR)?&lt;/h3&gt;
&lt;p&gt;This is where things get genuinely exciting — and where most beginner tutorials stop short.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Think of it like:&lt;/strong&gt; a restaurant table that's already set, with bread on it the moment you sit down (static shell) — while your actual hot meal (dynamic content) is still being cooked and gets brought out the second it's ready.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Cache Components&lt;/strong&gt; is Next.js's newer model for controlling caching explicitly, and it's what powers &lt;strong&gt;Partial Prerendering (PPR)&lt;/strong&gt; under the hood. Instead of choosing &lt;em&gt;one&lt;/em&gt; rendering strategy for an entire page, PPR lets a &lt;strong&gt;single page mix static and dynamic content together&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Here's the key idea: Next.js pre-renders a &lt;strong&gt;static &quot;shell&quot;&lt;/strong&gt; of your page instantly (the parts that don't depend on the current request), and then &lt;strong&gt;streams in the dynamic parts&lt;/strong&gt; as soon as they're ready — all without making the visitor wait for the slowest piece of data.&lt;/p&gt;
&lt;h4 id=&quot;step-1-enable-cache-components&quot;&gt;Step 1: Enable Cache Components&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// next.config.ts
import type { NextConfig } from &quot;next&quot;;

const nextConfig: NextConfig = {
  cacheComponents: true, // turns on the Cache Components / PPR model
};

export default nextConfig;
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; Once &lt;code&gt;cacheComponents&lt;/code&gt; is enabled, Next.js stops caching things implicitly. You now have to explicitly mark what should be cached using &lt;code&gt;&quot;use cache&quot;&lt;/code&gt; — this makes caching predictable instead of &quot;magic.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&quot;step-2-mark-cacheable-parts-with-use-cache-&quot;&gt;Step 2: Mark cacheable parts with &lt;code&gt;&quot;use cache&quot;&lt;/code&gt;&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// app/store/page.tsx
import { Suspense } from &quot;react&quot;;
import { CartStatus } from &quot;@/components/cart-status&quot;;

async function FeaturedProducts() {
  &quot;use cache&quot;; // this function's result gets cached and reused

  const res = await fetch(&quot;https://api.example.com/products/featured&quot;);
  const products = await res.json();

  return (
    &amp;lt;ul&amp;gt;
      {products.map((p: { id: string; name: string }) =&amp;gt; (
        &amp;lt;li key={p.id}&amp;gt;{p.name}&amp;lt;/li&amp;gt;
      ))}
    &amp;lt;/ul&amp;gt;
  );
}

export default function StorePage() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Welcome to the Store&amp;lt;/h1&amp;gt;

      {/* Static shell — cached, renders instantly */}
      &amp;lt;FeaturedProducts /&amp;gt;

      {/* Dynamic hole — depends on the current user, streams in separately */}
      &amp;lt;Suspense fallback={&amp;lt;p&amp;gt;Loading your cart...&amp;lt;/p&amp;gt;}&amp;gt;
        &amp;lt;CartStatus /&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What's happening here:&lt;/strong&gt; &lt;code&gt;FeaturedProducts&lt;/code&gt; has &lt;code&gt;&quot;use cache&quot;&lt;/code&gt; at the top, so Next.js treats it as part of the static shell — it's pre-rendered and reused across visitors, just like SSG. &lt;code&gt;CartStatus&lt;/code&gt;, on the other hand, depends on &lt;em&gt;who's&lt;/em&gt; visiting (their cart), so it's wrapped in &lt;code&gt;&amp;lt;Suspense&amp;gt;&lt;/code&gt;. Next.js sends the static shell (heading + featured products) &lt;strong&gt;instantly&lt;/strong&gt;, then streams in the cart status the moment it's ready — without blocking the rest of the page.&lt;/p&gt;
&lt;p&gt;This is the best of every world: the instant load of SSG, the freshness of SSR, all on the &lt;em&gt;same&lt;/em&gt; page, without you manually choosing one strategy for the whole route.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; You can control how long cached content lives using &lt;code&gt;cacheLife()&lt;/code&gt; (time-based expiry) and invalidate it on demand using &lt;code&gt;cacheTag()&lt;/code&gt; + &lt;code&gt;revalidateTag()&lt;/code&gt; — similar in spirit to ISR's &lt;code&gt;revalidate&lt;/code&gt;, but with much finer control over &lt;em&gt;what&lt;/em&gt; gets invalidated and &lt;em&gt;when&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;⚠️ &lt;strong&gt;Common Mistake:&lt;/strong&gt; Don't put &lt;code&gt;&quot;use cache&quot;&lt;/code&gt; on a function that returns data specific to the logged-in user with the default (shared) cache — that data could end up served to other users. Use &lt;code&gt;&quot;use cache: private&quot;&lt;/code&gt; for personalized cached content instead.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;quick-comparison&quot;&gt;Quick Comparison&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Strategy &lt;/th&gt;&lt;th&gt;When HTML is built &lt;/th&gt;&lt;th&gt;Best for &lt;/th&gt;&lt;th&gt;Server load per request &lt;/th&gt;&lt;th&gt;SEO&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SSR&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;On every request &lt;/td&gt;&lt;td&gt;Personalized, frequently changing data &lt;/td&gt;&lt;td&gt;High &lt;/td&gt;&lt;td&gt;Excellent&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;SSG&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Once, at build time &lt;/td&gt;&lt;td&gt;Content that rarely changes &lt;/td&gt;&lt;td&gt;None &lt;/td&gt;&lt;td&gt;Excellent&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;CSR&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Minimal shell on server, content rendered in browser after hydration &lt;/td&gt;&lt;td&gt;Highly interactive, non-SEO UI &lt;/td&gt;&lt;td&gt;Low (just the shell) &lt;/td&gt;&lt;td&gt;Depends&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;ISR&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;At build time + periodic background refresh &lt;/td&gt;&lt;td&gt;Content that changes occasionally &lt;/td&gt;&lt;td&gt;Low &lt;/td&gt;&lt;td&gt;Excellent&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Cache Components (PPR)&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Mixed — static shell instantly, dynamic parts streamed &lt;/td&gt;&lt;td&gt;Pages with both static and personalized sections &lt;/td&gt;&lt;td&gt;Only for the dynamic parts &lt;/td&gt;&lt;td&gt;Excellent&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;frequently-asked-questions&quot;&gt;Frequently Asked Questions&lt;/h3&gt;
&lt;h4 id=&quot;do-i-have-to-pick-one-strategy-for-my-whole-app-&quot;&gt;Do I have to pick one strategy for my whole app?&lt;/h4&gt;
&lt;p&gt;No — and that's the whole point of Cache Components. You can mix strategies &lt;em&gt;per page&lt;/em&gt;, and with PPR, even &lt;em&gt;within the same page&lt;/em&gt;. Most real-world Next.js apps use a combination of all of these depending on the route.&lt;/p&gt;
&lt;h4 id=&quot;is-isr-the-same-as-cache-components-&quot;&gt;Is ISR the same as Cache Components?&lt;/h4&gt;
&lt;p&gt;Not quite. ISR revalidates an entire page on a timer. Cache Components give you finer control — you choose exactly &lt;em&gt;which functions or components&lt;/em&gt; get cached, for how long, and how they get invalidated, all while letting other parts of the same page stay dynamic.&lt;/p&gt;
&lt;h4 id=&quot;will-cache-components-replace-isr-completely-&quot;&gt;Will Cache Components replace ISR completely?&lt;/h4&gt;
&lt;p&gt;For new projects using &lt;code&gt;cacheComponents: true&lt;/code&gt;, yes — &lt;code&gt;&quot;use cache&quot;&lt;/code&gt; with &lt;code&gt;cacheLife()&lt;/code&gt; covers what &lt;code&gt;revalidate&lt;/code&gt; used to do, plus more. Existing apps using the older caching model can keep working as-is, but new apps are encouraged to adopt Cache Components.&lt;/p&gt;
&lt;h4 id=&quot;does-ssr-hurt-my-seo-&quot;&gt;Does SSR hurt my SEO?&lt;/h4&gt;
&lt;p&gt;No — search engines see fully rendered HTML either way with SSR, SSG, or ISR. CSR is the one to watch, since content rendered purely in the browser can be harder for some crawlers to index reliably.&lt;/p&gt;
&lt;h4 id=&quot;how-do-i-know-which-strategy-a-page-is-using-&quot;&gt;How do I know which strategy a page is using?&lt;/h4&gt;
&lt;p&gt;Run &lt;code&gt;next build&lt;/code&gt; and check the build output — Next.js labels each route as Static, Dynamic, or Partial Prerendered (when Cache Components is enabled), so you can see exactly how each page will behave in production.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;💡 &lt;strong&gt;Tip:&lt;/strong&gt; Always test caching behavior with &lt;code&gt;next build &amp;amp;&amp;amp; next start&lt;/code&gt; rather than &lt;code&gt;next dev&lt;/code&gt; — caching behaves differently in development mode and can be misleading.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h3&gt;
&lt;p&gt;Rendering strategy isn't a one-time architectural decision anymore — it's a per-component choice. SSR gives you freshness, SSG gives you speed, CSR gives you interactivity, ISR gives you a balance between the two static options, and Cache Components (powered by PPR) let you combine all of it on a single page without compromise.&lt;/p&gt;
&lt;p&gt;Start simple: default to SSG/ISR for content-heavy pages, reach for SSR only when you genuinely need per-request freshness, and once you're comfortable, experiment with Cache Components to squeeze instant load times out of pages that still need personalized data.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;Decision Tree

Is content personalized?
→ SSR

Is content mostly static?
→ SSG

Does it update occasionally?
→ ISR

Is it highly interactive UI?
→ CSR

Need static + dynamic on same page?
→ Cache Components / PPR
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;useful-resources&quot;&gt;Useful Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/docs/app/api-reference/functions/fetch&quot;&gt;Next.js Fetch API Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/docs/pages/building-your-application/rendering/client-side-rendering&quot;&gt;Next.js CSR Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/docs/pages/building-your-application/rendering/server-side-rendering&quot;&gt;Next.js SSR Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/docs/pages/building-your-application/rendering/static-site-generation&quot;&gt;Next.js SSG Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://nextjs.org/docs/app/getting-started/caching&quot;&gt;Next.js Cache Components Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;continue-learning&quot;&gt;Continue Learning&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/saas-starter-architecture-nextjs&quot;&gt;SaaS Starter Architecture with Next.js 2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/sanity-nextjs-16-guide&quot;&gt;How to Integrate Sanity CMS with Next.js App Router&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://devstacked.tech/blog/how-to-add-clerk-authentication-in-nextjs-16&quot;&gt;How to Add Clerk Authentication in Next.js 16 (2026 Guide)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23657/next-js-rendering-strategies-explained-ssr-ssg-csr-isr-cache-components-2026</guid>
<pubDate>Tue, 28 Jul 2026 10:32:23 +0000</pubDate>
</item>
<item>
<title>DriftNotes — A Minimal Journaling System</title>
<link>https://coderlegion.com/8354/driftnotes-a-minimal-journaling-system</link>
<description>&lt;p&gt;&lt;em&gt;Note: published initially at: &lt;a rel=&quot;nofollow&quot; href=&quot;https://rohitfarmer.com/posts/2025/driftnotes/&quot;&gt;https://rohitfarmer.com/posts/2025/driftnotes/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For the past several years, I’ve been experimenting with different ways of maintaining a personal journal. I tried everything: paper notebooks, digital notes, blogging platforms, custom Markdown workflows, and even small private CMS-like systems. All of them worked to some extent, but none of them struck the balance I wanted.&lt;/p&gt;
&lt;p&gt;Most tools fall into one of two extremes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Social media&lt;/strong&gt;, which encourages short, impulsive posts that disappear into a timeline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Blogging platforms&lt;/strong&gt;, which feel too formal for quick daily notes or reflections.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I wanted something &lt;strong&gt;in between&lt;/strong&gt; — a space for small but meaningful entries, longer than a tweet but far less structured than a blog post. A place where I could drop thoughts, observations, or moments from each day without turning it into an essay.&lt;/p&gt;
&lt;p&gt;That search eventually led me to build something for myself: &lt;strong&gt;DriftNotes&lt;/strong&gt;. That I now use to generate my online journal at &lt;a rel=&quot;nofollow&quot; href=&quot;https://journal.rohitfarmer.com/&quot;&gt;https://journal.rohitfarmer.com/&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;why-i-created-driftnotes&quot;&gt;Why I Created DriftNotes&lt;/h3&gt;
&lt;p&gt;Over time, my journaling style became more fragmented. Some days I’d write paragraphs; other days, just a few lines and an image. But I still wanted:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a clean, minimalist reading experience&lt;/li&gt;
&lt;li&gt;local files I fully control&lt;/li&gt;
&lt;li&gt;a private-first workflow&lt;/li&gt;
&lt;li&gt;the ability to publish selected entries on my website&lt;/li&gt;
&lt;li&gt;less dependencies on centralized services&lt;/li&gt;
&lt;li&gt;fast builds, simple HTML, and complete portability&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;No existing tool truly matched this philosophy. So I built one.&lt;/p&gt;
&lt;p&gt;DriftNotes grew out of a simple idea:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Write your daily entries in plain Markdown, organized by month and year, and let a minimal static site generator turn them into a beautiful, searchable journal.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That’s all.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No database.&lt;/li&gt;
&lt;li&gt;No logins.&lt;/li&gt;
&lt;li&gt;No frameworks.&lt;/li&gt;
&lt;li&gt;No “rich text editors.”&lt;/li&gt;
&lt;li&gt;Just Markdown → HTML.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Initially, this started as a handful of scripts I wrote for my personal journaling site. Over time, I kept adding features that made sense for daily use — nothing more, nothing less.&lt;/p&gt;
&lt;p&gt;Eventually, I realized I had built something I wanted to share.&lt;/p&gt;
&lt;h3 id=&quot;how-driftnotes-works&quot;&gt;How DriftNotes Works&lt;/h3&gt;
&lt;p&gt;DriftNotes is intentionally simple to understand and use.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every year gets its own folder (like &lt;code&gt;2025/&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Every month is a Markdown file (&lt;code&gt;2025-11.md&lt;/code&gt;, &lt;code&gt;2025-12.md&lt;/code&gt;, etc.).&lt;/li&gt;
&lt;li&gt;Each entry starts with a heading that contains the date.&lt;/li&gt;
&lt;li&gt;You can optionally add tags or mark an entry as a draft (to not include in the HTML output).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The build script — just a single Python file — processes everything and generates a static website with:&lt;/p&gt;
&lt;h4 id=&quot;clean-html-pages-for-each-year&quot;&gt;Clean HTML pages for each year&lt;/h4&gt;
&lt;p&gt;Entries appear in reverse chronological order.&lt;/p&gt;
&lt;h4 id=&quot;tag-support&quot;&gt;Tag support&lt;/h4&gt;
&lt;p&gt;Add tags to entries and DriftNotes automatically creates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;tag pages&lt;/li&gt;
&lt;li&gt;a tag index&lt;/li&gt;
&lt;li&gt;tag pills under each entry&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;-on-this-day-page&quot;&gt;“On This Day” page&lt;/h4&gt;
&lt;p&gt;A fun way to revisit past moments from different years.&lt;/p&gt;
&lt;h4 id=&quot;dark-mode-toggle&quot;&gt;Dark mode toggle&lt;/h4&gt;
&lt;p&gt;No frameworks, no heavyweight UI — just clean CSS + a tiny JS file.&lt;/p&gt;
&lt;h4 id=&quot;offline-search-with-lunr-js&quot;&gt;Offline search with Lunr.js&lt;/h4&gt;
&lt;p&gt;Search your entire journal instantly, without sending anything to a server.&lt;/p&gt;
&lt;h4 id=&quot;rss-feed-for-the-latest-year&quot;&gt;RSS feed for the latest year&lt;/h4&gt;
&lt;p&gt;Useful if you or others want to follow your journal via an RSS reader.&lt;/p&gt;
&lt;h4 id=&quot;fully-static-output&quot;&gt;Fully static output&lt;/h4&gt;
&lt;p&gt;Everything goes into a &lt;code&gt;_site/&lt;/code&gt; folder (or wherever you choose).&lt;br&gt;
You can publish it anywhere: Nginx, GitHub Pages, Netlify (which I use), a pubnix, or even on a USB drive.&lt;/p&gt;
&lt;h3 id=&quot;how-i-use-driftnotes&quot;&gt;How I Use DriftNotes&lt;/h3&gt;
&lt;p&gt;On my local machine, I keep a folder structure like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;content/
  2024/
    2024-01.md
    2024-02.md
  2025/
    2025-12.md
    2025-11.md
config.yml
driftnotes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every day, I add a new entry under the appropriate month’s file. Some days it’s a paragraph; some days it’s several. I include images, tags, and small reflections.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-markdown&quot;&gt;# December 2025

## 2025-12-05
tags: snowday

We experienced our first snow day today here in North Bethesda, Maryland. It wasn't bad, and schools did not close, but when my kids left for school, it was still snowing. We talked about playing in the snow once they return from school, but I don't think there will be enough snow left in our courtyard by then.

![First snow of the season - 2025](https://files.mastodon.social/media_attachments/files/115/667/411/648/050/953/original/90e4a73e475de44e.jpg)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When I run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python3 driftnotes/build.py config.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;the system generates a complete journal site in &lt;code&gt;_site&lt;/code&gt; folder which I then serve through Netlify.&lt;/p&gt;
&lt;p&gt;I use it for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;my &lt;strong&gt;daily personal notes&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;short reflections that don’t belong on my main blog&lt;/li&gt;
&lt;li&gt;photo + text entries about family moments&lt;/li&gt;
&lt;li&gt;capturing ideas, inspirations, and small insights&lt;/li&gt;
&lt;li&gt;documenting hikes, trips, and things I learn along the way&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some entries I publish publicly. Some remain private.&lt;br&gt;
The beauty of DriftNotes is that &lt;strong&gt;the source files are yours&lt;/strong&gt; — portable, readable, and future-proof.&lt;/p&gt;
&lt;h3 id=&quot;now-available-for-others-to-use&quot;&gt;Now Available for Others to Use&lt;/h3&gt;
&lt;p&gt;DriftNotes was originally designed just for me, but I realized that others might appreciate a minimal tool that sits between social media and a traditional blog.&lt;/p&gt;
&lt;p&gt;So I’ve cleaned it up, documented it, added a proper structure, and released it publicly.&lt;/p&gt;
&lt;p&gt;If you like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;small tools&lt;/li&gt;
&lt;li&gt;plain text&lt;/li&gt;
&lt;li&gt;journaling&lt;/li&gt;
&lt;li&gt;static site generators&lt;/li&gt;
&lt;li&gt;privacy and simplicity&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;…then you might enjoy using DriftNotes.&lt;/p&gt;
&lt;p&gt;You can explore the repository here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rohitfarmer/driftnotes&quot;&gt;https://github.com/rohitfarmer/driftnotes&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It’s released under the MIT license, and the GitHub repository can be dropped directly into your own project. You only maintain your Markdown files and a simple &lt;code&gt;config.yml&lt;/code&gt; — DriftNotes handles the rest.&lt;/p&gt;
&lt;p&gt;If there’s community interest, I may extend it with more features. For now, it’s intentionally focused and minimal.&lt;/p&gt;
&lt;h3 id=&quot;closing-thoughts&quot;&gt;Closing Thoughts&lt;/h3&gt;
&lt;p&gt;DriftNotes reflects a philosophy that has increasingly shaped my online writing:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Not everything needs to be a blog post, and not everything should be a tweet.&lt;br&gt;
There’s value in the quiet, steady space in between.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you try DriftNotes, or build something with it, I’d love to hear from you.&lt;br&gt;
This little project already brings joy to my own journaling routine — I hope it can do the same for someone else.&lt;/p&gt;
</description>
<category>CLI Tools &amp; Scripts</category>
<guid isPermaLink="true">https://coderlegion.com/8354/driftnotes-a-minimal-journaling-system</guid>
<pubDate>Wed, 10 Dec 2025 21:19:41 +0000</pubDate>
</item>
<item>
<title>GhostCMS + Astro + Cloudflare for writing content</title>
<link>https://coderlegion.com/6501/ghostcms-astro-cloudflare-for-writing-content</link>
<description>&lt;p&gt;Just finished setting up GhostCMS and Astro integration!&lt;br&gt;
This feature will eventually replace the /blog section on my website, making it much easier for me to write and manage articles &lt;/p&gt;
&lt;p&gt;One of the most important things before releasing it to production is testing scheduled backups and restoring them.&lt;br&gt;
Since I’m using Dokploy, the backup setup is much easier, especially for the database.&lt;/p&gt;
&lt;p&gt;For the database, I’ve set up daily backups stored on Cloudflare R2. In the future, I plan to add more redundancy to make the backups safer and more reliable.&lt;/p&gt;
&lt;p&gt;Next, I’ll be running a few more test cases before officially releasing the GhostCMS and Astro integration.&lt;br&gt;
Of course, users won’t notice any difference, it’ll still be SSG (Static Site Generation) on the frontend &lt;/p&gt;
&lt;p&gt;Is anyone else here also using GhostCMS with Astro.js, or maybe GhostCMS with a Jamstack setup?&lt;/p&gt;
</description>
<category>Full-stack Dev</category>
<guid isPermaLink="true">https://coderlegion.com/6501/ghostcms-astro-cloudflare-for-writing-content</guid>
<pubDate>Thu, 16 Oct 2025 03:18:59 +0000</pubDate>
</item>
</channel>
</rss>