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?
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``()
orrevalidatePath()
- Can be manually re-rendered via
-
When refreshing the screen
state and reloading the screen does not change the data.
-
When we revalidate
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
- Using the Dynamic API
- 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;
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.