5

I really can't figure out what Next.js have for plan to make cookies work in client components. The current docs they have for cookies is not compatible with client components.

In server components, you can read and set the cookies using server actions. With client components, you can set them with server actions, but it doesn't look like you can read the cookies. Trying to use cookies().get(name) results in the error below.

You're importing a component that needs next/headers. That only works in a Server

...

Component but one of its parents is marked with "use client", so it's a Client Component. One of these is marked as a client entry with "use client":
./app/client/hooks/useCookies.ts
./app/client/components/SomeComponent.tsx

Of course, you can add a server actions to read the cookie, that I have tested to see that it works. But I refuse to mess up my code with async function handling and unnecessary server trips just to read a cookie that already lies right there in the browser.

I have also tried libraries, like cookies-next, which seems to be best maintained and working lib for Next.js cookies. That one works to read and set cookies in client components on client side.

The issue using that library is that the initial rendering flickers, since that library doesn't seem to be able to read the cookies server side with app routing. Even if I manually enable dynamic routing using Route Segment Config all cookies are undefined on the initial SSR request. It's first when I reach the client the cookies are found and read correctly. And with static rendering, cookies obviously cannot be read.

I have also tried setting the cookies using Next.js built in cookie system and then reading them with cookies-next library in my client components. Even using that solution, cookies are undefined on the initial SSR request.

Therefore I wonder, can anyone tell me if there is a way to make these two conditions work together when using Next.js app routing?

  • Being able to read cookies in a client component directly from the browser (no server action)
  • Being able to read cookies in a client component, on the server in the initial SSR request, when dynamic routing is enabled

Do I really have to read cookies in a server component and pass the cookie values down to client components as serialised values? Which also means that they will not be automatically updated when I set new values for the cookie.

I feel there is something working very wrong here, or I hope I have totally missed out something. Whole purpose with cookies is that you should be able to read them both client and server side.

Dennis Persson
  • 843
  • 7
  • 23

3 Answers3

1

I have also tried and gained experience: client cookies are available in client components, and server cookies are available in server components. These different cookies are not accessible to each other. It looks like this is another flaw in NextJS

Tony
  • 11
  • 2
0

Please mention what you have tried. I don't know if I understood the problem properly but I hope this helps.

import { cookies } from 'next/headers'
 
export default function Page() {
  const cookieStore = cookies()
  const theme = cookieStore.get('theme')
  return '...'
}

Please check out Next Documentatoin for more information.

  • I have already mentioned several things I've tried in the question on topic. The problem is that I want to be able to read cookies both on client and server. Your code works in Server Components on the server, but next/headers module is not available in Client Components so it cannot be used to read cookies on the client. – Dennis Persson Aug 19 '23 at 09:42
0

One common confusion about Client Components is that they are also rendered on the server (once, on initialization), and sent back as pure HTML/CSS.
They are hydrated in the browser later to add javascript.

See Next.js - What is Streaming?

At that point, javascript libraries like cookies-next or js-cookie won't run as you would expect since they are not actually running in your browser (there is no window or document, we are server-side).

That means that the very first render may be incomplete in a Client Component, and it won't load the data you expect it to.
That's ok in most cases because it only last for a short instant. The idea is simply to deliver the best static placeholder/shell possible, but in your case that's what is causing the flickering effect

You can observe it yourself by disabling javascrit in your browser. You will see all your Client Components load, but probably not in the state you expected.

In short:

  • A Client Component, have access to document cookies via cookies-next (not via next/headers), but the first (static) render won't get any data from it.
  • A Server Component, on the other hand, have access to HTTP cookies via next/headers (but not via cookies-next).

You cannot have both behavior in the same component
I believe that's by design, there is no way for a function to work in both spaces.


But, you can build a Client Component and wrap it into Server Component, like so:

page.tsx

import { cookies } from 'next/headers'
import { MyClientComponent } from './MyClientComponent'

export default function Page() {
    // Server-side: based on HTTP resquest cookie only
    const bgColor = cookies().get('bgColor')?.value
    return <MyClientComponent initial={{ bgColor }} />
}

MyClientComponent.tsx

'use client'

import { getCookie, setCookie } from 'cookies-next'
import { useState, useEffect } from 'react'

export function MyClientComponent({
    initial,
}: {
    initial: { bgColor?: string }
}) {
    const [bgColor, setBgColor] = useState(
        getCookie('bgColor')?.toString() ?? initial.bgColor ?? '',
    )

    useEffect(() => {
        setCookie('bgColor', bgColor)
    }, [bgColor])

    return (
        <input
            style={{ backgroundColor: bgColor }}
            value={bgColor}
            onChange={({ target }) => {
                setBgColor(target.value)
            }}
        />
    )
}

Also:

  • Always use Link, so that the page is not refreshed while navigating, limiting the flickering effect to the very first (server-side) rendering only
teos
  • 109
  • 1
  • 6