Server Component Rendering Strategies

profile image

Understand server component rendering strategies and precautions. Also covers issues that may arise when using with Parallel Routes and their solutions.

This post has been translated by Jetbrains's Coding Agent Junie junie logoPlease let me know if there are any mistranslations!

Server Component Rendering

Traditional React components, known as client components, re-render when their own state or their parent's state changes. However, hooks cannot be used in server components. This means server components do not have state. As shown in the example below, even if the parent's state changes, the server component does not re-render. So when do server components re-render?

server-component-rendering-strategies-example1

Server Component Rendering Strategies

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

Static Rendering

Static Rendering is Next.js's default rendering strategy. The key feature of this strategy is that components are rendered once at build time, and the results are reused.

  • Build Time Rendering
    • Server components are rendered at application build time
    • The rendered results are cached in CDN and provided to all users
  • Data Immutability
    • The data of a once-rendered component basically does not change
    • The same data is displayed even after refreshing the page
    • Client state changes do not affect server component data
  • Revalidation Mechanism
    • Manual re-rendering is possible through revalidateTag() or revalidatePath()
  1. When refreshing the screen

    server-component-rendering-strategies-example2

    You can see that even if you change the state and reload, the data does not change.

  2. When revalidating

    server-component-rendering-strategies-example3

    You can see that when Next.js's revalidateTag is executed, it invalidates the server component's cache, fetches the data again, and re-renders.

Dynamic Rendering

Dynamic Rendering is Next.js's rendering strategy that renders components anew on the server for each user request.

Server components automatically switch to Dynamic Rendering if they satisfy either of the following two conditions:

  1. Using Dynamic API
  2. Finding { cache: 'no-store' } in fetch options

Rendering Strategy Decision Table

Using Dynamic APIData CachingRendering Result
❌ No✅ CachedStatic
✅ Yes✅ CachedDynamic
❌ No❌ Not CachedDynamic
✅ Yes❌ Not CachedDynamic

In other words, Static Rendering only occurs when not using Dynamic API and applying caching to fetch.

Dynamic APIs

Dynamic APIs refer to APIs that depend on the context at the time of request. Let's look at the main Dynamic APIs.

1. cookies()

typescript
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()

typescript
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

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

Precautions When Using with Parallel Routes

When controlling modals using searchParams as in the example below, problems can occur due to Dynamic Rendering:

  • Server component re-renders when searchParams change
  • Modal delay due to data fetching
  • Data changes simultaneously with modal opening
typescript
// 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;

server-component-rendering-strategies-example4

Solution

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

typescript
'use client';

function ProductList() {
  const openModal = () => {
    // Directly change URL without going through Next.js routing
    history.pushState(null, '', `?modal=true`);
  };

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

Using Link or useRouter triggers navigation for the changed URL, causing the rendering logic to run anew. However, using the History API changes only the URL without going through Next's routing system, so the rendering logic doesn't run, but Next supports synchronization with useSearchParams and usePathname, allowing detection of URL changes and display of the modal.

Conclusion

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

❤️ 0
🔥 0
😎 0
⭐️ 0
🆒 0