The Reason Why:
...HTTP does not allow setting cookies after streaming begins, so the .set() method must be used in a Server Action or Route Handler.
The issue with setting cookies arises from the flow of an HTTP response and React's new feature known as Streaming.
When a Next.js server component runs, it has already generated the HTTP response consisting of a response header and body (https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages#headers).
It is important to note that once the HTTP response commences, the HTML or data being sent to the browser is already rendered in the response body. This implies that any server methods (such as setting a server cookie) would have executed before the HTTP response was constructed.
The crux of the matter lies in how React streaming functions: - simplified
Similar to progressive hydration, streaming is a rendering technique that enhances SSR performance by sending chunks of HTML from the node server to the client as they are created.
Hence, if a slow async sub-component attempts to set a cookie on the server during client-side streaming, it becomes impossible due to the ongoing stream like an AJAX request fetching HTML from the server.
For instance, when there are 20 slow components, React delivers the prerendered HTML along with the Suspense
fallback HTML.
<FooBarComponent />
<Suspense fallback={<div>Loading...</div>}>
<FreshlyLoadedContent />
</Suspense>
The HTTP response renders as follows:
<div>foo bar</div>
<div id="8:0">Loading...</div>
Any remaining streamed HTML is handled on the client side. React replaces sections of HTML (using an id=""
for each streamed component) one by one in the DOM post the completion of slow asynchronous components' requests and renderings. Consequently, some parts of the page display a loading state while others are already rendered.
Once the streaming of a particular component finishes, it updates the div content as such:
<div>foo bar</div>
<div id="8:0">Fresly loaded content!</div>
The major advantage of this approach...
...is that browsers commence processing and displaying streamed content or partial responses earlier, resulting in reduced and consistent Time To First Byte (TTFB). The early parsing and rendering lead to swift First Paint (FP) and First Contentful Paint (FCP) owing to progressive rendering.
Consequently,
https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#behavior - states:
Server actions can be triggered using the action attribute within a form element:
and
Server Actions are not restricted to forms and can be activated through event handlers, useEffect, third-party libraries, and other form elements like buttons.
This highlights that Server Actions specifically need to be initiated from client-side components. Think of submitting a form to the server or invoking a secure server-based API function to transmit sensitive Credit Card details. Does that make sense?
In this scenario, you must develop a client component and trigger your server action exampleAction()
, potentially utilizing useEffect
or another client-based action (e.g., onClick
). This ensures the server performs an operation based on the client's action. This serves as a workaround.
You can also leverage middleware to both read and set a cookie prior to Next.js initiating the HTTP response back to the client and commencing any streaming:
import { NextRequest, NextResponse } from "next/server";
export function middleware(request){
let response = NextResponse.next()
response.cookies.set("foo", "bar")
return response;
}