Several resources online state that Server Components (“SC”) never re-render.
However, the following MRE says otherwise. When a button is clicked, a Server Action runs and, as a result, server-side log statements that only appear inside SC function bodies do fire off.
That should technically not happen, as the SCs should be fixed and immutable within one request (for dynamic routes).
What am I missing here?
MRE: https://github.com/magnusriga/server-action
SC:
import { ClientComponent2 } from "./client-component2";
import { ClientComponent } from "./client-component";
import { headers } from "next/headers";
function wait(ms: number) {
return new Promise<number>((resolve) => setTimeout(() => {
const _headers = new Map(headers());
resolve(Math.floor(Math.random() * 100));
console.log('headers in promise after resolve', _headers);
}, ms))
}
export default async function Home() {
// const promise = wait(3000);
const value = await wait(3000);
console.log('headers in component body', new Map(headers())); // This runs again when ClientComponent2 re-renders.
return (
<div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
{/* <ClientComponent promise={promise}/> */}
<ClientComponent2 value={value}/>
</main>
</div>
);
}
CC:
'use client'
import { useEffect, useState } from "react";
import { setCookiesAction } from "@/action";
import { useRouter } from "next/navigation";
export function ClientComponent({promise}: {promise: Promise<number>}) {
const router = useRouter();
const [data, setData] = useState<number | null>(null);
useEffect(() => {
console.log('in useEffect, promise is', promise)
promise.then((data) => {
console.log('promise resolved inside useEffect, setting data state next, data is:', data)
setData(data)
})
}, [promise]);
async function actionHandler() {
console.log('before running SA')
const saReturn = await setCookiesAction(data ?? 0)
console.log('after SA, return value was', saReturn)
setData(saReturn)
// router.refresh()
}
return (
<div className="bg-black/[.05] dark:bg-white/[.06] p-4 rounded text-sm">
{data === null ? "Loading..." : `Random number: ${data}`}
<button onClick={actionHandler} className="block mt-4 p-2 bg-black/[.1] dark:bg-white/[.1] rounded text-white dark:text-black"> Click To Set Cookies</button>
</div>
);
}
SA:
'use server'
import { revalidatePath } from "next/cache"
import { cookies } from "next/headers"
export async function setCookiesAction(num: number) {
cookies().set('name', 'value')
// revalidatePath('/')
return num
}
2
Several resources online state that Server Components (“SC”) never
re-render.
the above statement might address this
Server Rendering Strategies There are three subsets of server
rendering: Static, Dynamic, and Streaming.Static Rendering (Default) With Static Rendering, routes are rendered
at build time, or in the background after data revalidation. The
result is cached and can be pushed to a Content Delivery Network
(CDN). This optimization allows you to share the result of the
rendering work between users and server requests.
if route is not dynamic, content will be cached. that is why it will not be rerendered. if you read this part:
Dynamic Rendering With Dynamic Rendering, routes are rendered for each
user at request time.Dynamic rendering is useful when a route has data that is personalized
to the user or has information that can only be known at request time,
such as cookies or the URL’s search params.
now the key thing is here:
Switching to Dynamic Rendering
During rendering, if a dynamic function or uncached data request is
discovered, Next.js will switch to dynamically rendering the whole
route.
in your case cookies.set
and headers
are dynamic functions:
Dynamic Functions
Dynamic functions rely on information that can only be known at
request time such as a user’s cookies, current requests headers, or
the URL’s search params. In Next.js, these dynamic APIs are:
- cookies()
- headers()
- unstable_noStore()
- unstable_after():
- searchParams prop
When you click on the button your action runs. actions are also http requests. Are Next.js 13.4 Server Actions just a way to run api calls on the server side instead of the client side?
also from the docs:
Behind the scenes, actions use the POST method, and only this HTTP
method can invoke them.
since it is a dynamic route and you are making a request, it rerenders since a dynamic function is discovered, next.js will render the “WHOLE ROUTE` as mentioned here:
During rendering, if a dynamic function or uncached data request is
discovered, Next.js will switch to dynamically rendering the whole
route.
4