Server Component Rendering Strategy

🌐

This post has been translated by DeepL . Please let us know if there are any mistranslations!

Rendering Server Components

Client components, the traditional components of React, render when their state, or the state of their parent, changes. However, you can't use hooks on server components, which means that server components don't have state. As shown in the example below, the server component does not re-render when the parent's state changes. So when does a Server Component re-render?

AnimatedImage.gif

Server Component Rendering Strategy

According to the official Next.js documentation, there are three rendering strategies: Static, Dynamic, and Streaming.

Static Rendering

Static Rendering is the default rendering strategy for Next.js. The key feature of this strategy is that it renders components once at build time and reuses the result.

  • Build-time rendering

    • Server components are rendered at application build time
    • The rendered results are cached on the CDN and served to all users
  • Data immutability

    • Once rendered, the data in a component is essentially immutable.
    • Page refreshes show the same data
    • Client state changes don't affect data in server components
  • Revalidation mechanism

    • Can be manually re-rendered via revalidateTag``() or revalidatePath()
  1. When refreshing the screen

    AnimatedImage.gif

    state and reloading the screen does not change the data.

  2. When we revalidate

    AnimatedImage.gif

    When we run the revalidateTag in Next.js, we can see that it invalidates the server component's cache, fetches the data again, and re-renders it.

Dynamic Rendering

Dynamic Rendering is a rendering strategy in Next.js that renders a new component on the server for each user request.

Server components automatically switch to Dynamic Rendering when either of the following two conditions are met

  1. Using the Dynamic API
  2. Found { cache: 'no-store' } in the fetch option

Rendering strategy decision table

| Using the Dynamic API | Data Caching | Rendering Results | | -------------------- | --------------- | --------------- | --------------- | | ❌ No | βœ… Cached | Static | | βœ… Yes | βœ… Cached | Dynamic | | ❌ No | ❌ Not Cached | Dynamic | | βœ… Yes | ❌ Not Cached | Dynamic |

This means that you can only achieve Static Rendering by not using the Dynamic API and applying caching to fetches.

Dynamic APIs

Dynamic APIs are APIs that rely on context at the time of the request. Let's take a look at the main Dynamic APIs

1. cookies()

import { cookies } from 'next/headers';

async function Component() {
  const cookieStore = cookies();
  const theme = cookieStore.get('theme');
  return <div>Current theme: {theme?.value}</div>;
}

2. headers()

import { headers } from 'next/headers';

async function Component() {
  const headersList = headers();
  const userAgent = headersList.get('user-agent');
  return <div>Accessing from: {userAgent}</div>;
}

3. searchParams

// app/page.tsx
export default function Page({
  searchParams,
}: {
  searchParams: { query: string };
}) {
  return <div>Search query: {searchParams.query}</div>;
}

Cautions when using with Parallel Routes

When using searchParams to control modals, as in the example below, you may encounter issues with Dynamic Rendering.

  • Server components re-render when changing searchParams
  • Modal delays due to data fetching
  • Data changes at the same time as the modal opens
// app/@modal/default.tsx
'use client'

import {useSearchParams} from "next/navigation";
import {Modal} from "@/app/@modal/(.)post/[id]/modal";

function Default() {
  const searchParam = useSearchParams();
  const isModalOpen = searchParam.get('modal') === 'true';

  if (!isModalOpen) {
    return null;
  }

  return (
    <Modal>modal!!</Modal>
  );
}

} export default Default;

AnimatedImage.gif

Workaround

The simplest and most effective way is to bypass Next.js's routing system and use the browser's built-in History API directly.

'use client';

function ProductList() {
  const openModal = () => {
    // Change the URL directly, bypassing Next.js routing
    history.pushState(null, '', `?modal=true`);
  };

  return (
    <div> <button onClick={open
      <button onClick={openModal}>Open Modal</button>
    </div> </div>
  );
}

UsingLink or useRouter would cause navigation to occur for the changed URL, spinning up new rendering logic. However, using the History API doesn't trigger rendering logic because it only changes the URL without going through Next's routing system, but it does allow Next to synchronize with useSearchParams and usePathname so that it can detect the URL change and display the modal.

Closing thoughts

Server components are an important rendering optimization strategy in Next.js, but if used incorrectly, they can cause unintended re-renders or performance degradation. It's important to understand these rendering strategies and utilize them appropriately in order to use server components effectively.