Static and Dynamic Rendering

Hello, everyone, and welcome to our deep dive into rendering patterns and caching! Today, we'll explore how these concepts work together to make our web applications fast and efficient, using our blog website as a running example. This is a crucial topic for any web developer, as it directly impacts user experience and website performance.

1. Introduction

Imagine you're browsing the web and stumble upon a blog with hundreds of articles. You click on a category, say "Technology," and the page loads instantly. How does the website deliver that content so quickly? The answer lies in rendering patterns and caching strategies. These techniques allow us to pre-build parts of our website and store them for later use, dramatically speeding up page load times. Without these strategies, our blog would be slow and unresponsive, leading to frustrated users and lost readership.

2. Optimising Apps with Rendering Strategies

2.1 Static Rendering: Pre-building Our Blog

Let's start with static rendering. Think of this as pre-baking a cake. We prepare the entire cake (our webpage) ahead of time and store it. When someone wants a slice (visits the page), we simply serve it.

Simple Example: Imagine our blog has a page listing all categories. This page rarely changes. We can statically render this page at build time. When a user visits /categories, they receive the pre-rendered HTML.

Advanced Example: We can also statically render individual blog posts. If a post's content is unlikely to change frequently, like an introductory post about the blog itself, we can render it at build time. The route /blog/welcome-post would be pre-built.

How it Works: During the build process, Next.js generates HTML for these routes and stores them. When a user requests the page, the server instantly serves the pre-rendered HTML.

When to Use: Use static rendering for pages with content that doesn't change frequently, like about us pages, contact pages, or blog post lists.

When Not to Use: Avoid static rendering for pages with user-specific data or frequently changing content, like a user's profile page or a live news feed.

Our Blog Example: Our /categories page is perfect for static rendering. It lists the blog categories, which are unlikely to change often. We can also statically render some of the blog posts themselves, particularly introductory or evergreen content

// app/categories/page.tsx
export default function CategoriesPage() {
  return (
    <ul>
      <li>Technology</li>
      <li>Travel</li>
      <li>Food</li>
    </ul>
  );
}

// app/blog/welcome-post/page.tsx
export default function WelcomePostPage() {
    return (
        <div>
            <h1>Welcome to our Blog!</h1>
            <p>This is an introductory post...</p>
        </div>
    )
}

2.2 Dynamic Rendering: Building on Demand

Now, let's look at dynamic rendering. This is like ordering a custom-made pizza. We prepare the pizza (webpage) only when someone orders it (requests the page).

Simple Example: Imagine a blog post page that displays the number of views. This number changes constantly. We need to render this page dynamically when a user requests it.

Advanced Example: Filtering blog posts by tag. When a user visits /blog?tag=react, we need to dynamically fetch and filter the posts based on the tag query parameter.

How it Works: Next.js renders the page on the server when a user requests it.

When to Use: Use dynamic rendering for pages with user-specific data, frequently changing content, or content dependent on request parameters.

When Not to Use: Avoid dynamic rendering for pages with static content, as it would be unnecessarily slower than static rendering.

Our Blog Example: Our blog post pages with view counts and the filtered blog post pages are examples of where dynamic rendering is necessary. The content depends on the specific post or the filter applied.

// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';

async function getPost(slug: string) {
  const res = await fetch(`https://api.example.com/posts/${slug}`);
  if (!res.ok) {
    throw new Error('Failed to fetch post');
  }
  return res.json();
}

export default async function PostPage({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug);

  if (!post) {
    notFound();
  }

  return (
    <div>
      <h1>{post.title}</h1>
      <p>Views: {post.views}</p> {/* Dynamic view count */}
      <p>{post.content}</p>
    </div>
  );
}

// app/blog/page.tsx
import { useSearchParams } from 'next/navigation'

async function getPosts(tag?: string) {
    let url = "https://api.example.com/posts"
    if(tag) {
        url += `?tag=${tag}`
    }
    const res = await fetch(url);
    if (!res.ok) {
        throw new Error('Failed to fetch posts');
    }
    return res.json();
}

export default async function BlogPage() {
    const searchParams = useSearchParams()
    const tag = searchParams.get('tag')
    const posts = await getPosts(tag)

    return (
        <ul>
            {posts.map(post => (
                <li key={post.slug}><a href={`/blog/${post.slug}`}>{post.title}</a></li>
            ))}
        </ul>
    )
}

2.3. When does Next.js render Statically and when Dynamically?

It's all about how you fetch data and use certain Next.js features. Let's break it down:

2.3.1 Static Rendering:

  1. No Data Fetching at Runtime: If your page doesn't need to fetch any data after the initial page load, it's likely already statically rendered. Think of an "About Us" page with fixed content.
  2. <strong>generateStaticParams</strong> (for Dynamic Routes): If you have dynamic routes (e.g., /blog/[slug]) and you want to pre-render specific pages at build time, you use generateStaticParams. This function tells Next.js which pages to generate statically. If you return all possible paths, the site is fully statically generated. If you return some, some are static, some are dynamic. If you return an empty array, all are dynamic.   

    // app/blog/[slug]/page.tsx
    export async function generateStaticParams() {
      return [{ slug: 'my-first-post' }, { slug: 'another-post' }]; // Static pages
    }
    
    export default function PostPage({ params }: { params: { slug: string } }) {
      //...
    }
    
  3. No Dynamic APIs: Avoid using dynamic APIs like cookies, headers, or the searchParams prop within your page if you want it to be static. These rely on runtime information.

2.3.2. Dynamic Rendering:

  1. Data Fetching at Runtime: If your page needs to fetch data after the initial page load (e.g., fetching a user's profile or displaying real-time updates), it will be dynamically rendered.   
  2. Dynamic APIs: Using cookies, headers, or the searchParams prop in your page will automatically make it dynamic. These provide information only available at request time.

    // app/blog/page.tsx
    import { useSearchParams } from 'next/navigation';
    
    export default function BlogPage() {
      const searchParams = useSearchParams(); // Makes the page dynamic
      const tag = searchParams.get('tag');
      //...
    }
    
  3. <strong>cache: 'no-store'</strong>: Setting the cache option in a fetch call to 'no-store' forces the data to be fetched on every request, making the page dynamic.

    fetch('/api/data', { cache: 'no-store' }); // Forces dynamic rendering
  4. <strong>dynamic = 'force-dynamic'</strong>: This route segment config option forces dynamic rendering for the entire route segment.

    // app/blog/[slug]/page.tsx
    export const dynamic = 'force-dynamic';
    
    export default function PostPage() {
      //...
    }
    

In essence, if your page relies on anything that can only be known at request time, it will be dynamic. If it can be fully determined at build time, it can be static. Next.js intelligently handles this for you based on your code!

 

Slides

Hello, everyone, and welcome to our deep dive into rendering patterns and caching! Today, we'll explore how these concepts work together to make our web applications fast and efficient, using our blog website as a running example. This is a crucial topic for any web developer, as it directly impacts user experience and website performance.


Imagine you're browsing the web and stumble upon a blog with hundreds of articles. You click on a category, say "Technology," and the page loads instantly. How does the website deliver that content so quickly? The answer lies in rendering patterns and caching strategies. These techniques allow us to pre-build parts of our website and store them for later use, dramatically speeding up page load times. Without these strategies, our blog would be slow and unresponsive, leading to frustrated users and lost readership.


These techniques allow us to pre-build parts of our website and store them for later use, dramatically speeding up page load times. In this section, we will cover rendering strategies, we will cover caching with data fetching.


We will use a blog website as a running example.


Let's start with static rendering. Think of this as pre-baking a cake. We prepare the entire cake (our webpage) ahead of time and store it. When someone wants a slice (visits the page), we simply serve it.


Imagine our blog has a page listing all categories. This page rarely changes. We can statically render this page at build time. When a user visits /categories, they receive the pre-rendered HTML.


We can also statically render individual blog posts. If a post's content is unlikely to change frequently, like an introductory post about the blog itself, we can render it at build time. The route /blog/welcome-post would be pre-built.


Use static rendering for pages with content that doesn't change frequently, like about us pages, contact pages, or blog post lists.


Avoid static rendering for pages with user-specific data or frequently changing content, like a user's profile page or a live news feed.


Now, let's look at dynamic rendering. This is like ordering a custom-made pizza. We prepare the pizza (webpage) only when someone orders it (requests the page).


In our blog example, we want to display the number of views. This number changes constantly. We need to render this page dynamically when a user requests it.


Filtering blog posts by tag. When a user visits /blog?tag=react, we need to dynamically fetch and filter the posts based on the tag query parameter.


Using useSearchParams makes this page dynamic.


Use dynamic rendering for pages with user-specific data, frequently changing content, or content dependent on request parameters.


Avoid dynamic rendering for pages with static content, as it would be unnecessarily slower than static rendering.


So, how does NextJS decide when to render a route statically and when dynamically?


Pages with no runtime data fetching, using generateStaticParams (for dynamic routes), and avoiding dynamic APIs are typically static.


Pages with runtime data fetching, using dynamic APIs like cookies, headers, or searchParams, using cache: 'no-store', or setting dynamic = 'force-dynamic' are typically dynamic.


Rendering patterns are crucial for web performance.


Use static rendering for static content and dynamic rendering for dynamic content.


Static content can be intelligently distributed across CDN networks for instantaneous delivery!


NextJS intelligently handles rendering based on your code.


Understanding these concepts is essential for building efficient web applications.