vercel / next.js

The React Framework
https://nextjs.org
MIT License
127.03k stars 26.99k forks source link

no way to set cookies on server side (in server component) (using server action) #62800

Closed nicitaacom closed 8 months ago

nicitaacom commented 8 months ago

Link to the code that reproduces this issue

https://github.com/nicitaacom/acc2-mvp-set-cookie-on-ssr

To Reproduce

demo - http://127.0.0.1:3000/

mvp - https://silver-space-cod-q956xqw99gr2947.github.dev/

github - https://github.com/nicitaacom/acc2-mvp-set-cookie-on-ssr

Variant 1

  1. create next js project with pnpx create-next-app@latest
  2. paste this code in app/actions/setAnonymousId.ts
    
    "use server"

import { cookies } from "next/headers"

export async function setAnonymousId() { cookies().set("anonymousId", anonymousId_${crypto.randomUUID()}) }

3. paste this code in `app/utils/helpersSSR.ts`
```ts
"use server"
//This helpers may be used on server side only

import { cookies } from "next/headers"

/**
 *
 * @returns return cookie value by cookie name (type of string)
 */
export async function getCookie(name: string): Promise<string | undefined> {
  const cookieStore = cookies()
  return cookieStore.get(name) ? cookieStore.get(name)?.value : undefined
}

export async function setCookie(name: string, value: string, expiresInMinutes?: number) {
  // const oneWeek = 7 * 24 * 60 * 60 * 1000
  cookies().set(name, value, { expires: expiresInMinutes ? expiresInMinutes : undefined })
}
  1. paste this code in app/layout.tsx
    
    import "./globals.css"

import type { Metadata } from "next" import { Inter } from "next/font/google" import { setAnonymousId } from "@/actions/setAnonymousId" import { getCookie } from "./utils/helpersSSR" const inter = Inter({ subsets: ["latin"] })

export const metadata: Metadata = { title: "TODO - rename project", description: "description", }

export default async function RootLayout({ children }: { children: React.ReactNode }) { const anonymousId = await getCookie("anonymousId") if (!anonymousId) { async function anonymousId() { "use server" await setAnonymousId() } await anonymousId() }

return (

{children}

) }


See error

Error: Cookies can only be modified in a Server Action or Route Handler. Read more: https://nextjs.org/docs/app/api-reference/functions/cookies#cookiessetname-value-options at setAnonymousId (./app/actions/setAnonymousId.ts:14:61) at $$ACTION_0 (./app/layout.tsx:58:82) at RootLayout (./app/layout.tsx:32:151)


<br/>
<br/>

## Variant 2
1. `git clone https://github.com/nicitaacom/acc2-mvp-set-cookie-on-ssr/blob/development/`
2. `pnpm i`
3. `pnpm dev`
4. See error

### Current vs. Expected behavior

### Current
I got error

Error: Cookies can only be modified in a Server Action or Route Handler. Read more: https://nextjs.org/docs/app/api-reference/functions/cookies#cookiessetname-value-options at setAnonymousId (./app/actions/setAnonymousId.ts:14:61) at $$ACTION_0 (./app/layout.tsx:58:82) at RootLayout (./app/layout.tsx:32:151)


### Expected
I got no error and set cookie in server component using "use server" instead of route handler

### Provide environment information

```bash
Operating System:
  Platform: linux
  Arch: x64
  Version: #19~22.04.1-Ubuntu SMP Wed Jan 10 22:57:03 UTC 2024
Binaries:
  Node: 20.11.0
  npm: 10.2.4
  Yarn: 1.22.19
  pnpm: 8.15.1
Relevant Packages:
  next: 14.1.0
  eslint-config-next: 14.1.1
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.3.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

Data fetching (gS(S)P, getInitialProps), Middleware / Edge (API routes, runtime)

Which stage(s) are affected? (Select all that apply)

next dev (local)

Additional context

No response

JesseKoldewijn commented 8 months ago

Just by reading the provided error, it seems like there is an issue within the way you're calling the server action you're trying to use. Please make sure to check the docs which show some example uses and such.

https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations

nicitaacom commented 8 months ago

Just by reading the provided error, it seems like there is an issue within the way you're calling the server action you're trying to use. Please make sure to check the docs which show some example uses and such.

https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations

And what I'm doing wrong?

I know how to use server actions


If I will call it like this - function will be never called

image


I tried to do it in different ways like this image

nicitaacom commented 8 months ago

Also it would be really really nice if you add some minimal example for your code here

image

nicitaacom commented 8 months ago

Also I want to add additional context (because in error you see Cookies can only be modified in a Server Action or Route Handler) - so I decided to do it in route handler

app/api/set-anonymous-id/route.ts

import { setAnonymousId } from "@/actions/setAnonymousId"
import { cookies } from "next/headers"
import { NextResponse } from "next/server"

export async function POST() {
  cookies().set("anonymousId", `anonymousId_${crypto.randomUUID()}`)

  return NextResponse.json({ status: 200 })
}

I tried await axios.post("/api/set-anonymous-id") in layout.tsx And got this error Error: An error occurred in the Server Components render but no message was provided

And I tired to do it in middleware.ts and got this error

AxiosError: There is no suitable adapter to dispatch the request since :
- adapter xhr is not supported by the environment
- adapter http is not available in the build

If I use await fetch("/api/set-anonymous-id") then I get this error TypeError: Failed to parse URL from /api/set-anonymous-id

JesseKoldewijn commented 8 months ago

Also I want to add additional context (because in error you see Cookies can only be modified in a Server Action or Route Handler) - so I decided to do it in route handler

app/api/set-anonymous-id/route.ts


import { setAnonymousId } from "@/actions/setAnonymousId"

import { cookies } from "next/headers"

import { NextResponse } from "next/server"

export async function POST() {

  cookies().set("anonymousId", `anonymousId_${crypto.randomUUID()}`)

  return NextResponse.json({ status: 200 })

}

I tried await axios.post("/api/set-anonymous-id") in layout.tsx

And got this error Error: An error occurred in the Server Components render but no message was provided

And I tired to do it in middleware.ts and got this error


AxiosError: There is no suitable adapter to dispatch the request since :

- adapter xhr is not supported by the environment

- adapter http is not available in the build

If I use await fetch("/api/set-anonymous-id") then I get this error TypeError: Failed to parse URL from /api/set-anonymous-id

Relative urls don't work from the server. With regards to the rest, I can have a look later today into the root issue at hand.

JesseKoldewijn commented 8 months ago

@nicitaacom Below you'll find a demo repo which shows you two ways of updating cookies from the server.

https://github.com/JesseKoldewijn/next-cookie-setter-repro

One implementation uses a server action to update the cookie and the other uses fetch to fire a http POST request to a route handler which updates the cookie that way.

JesseKoldewijn commented 8 months ago

Also it would be really really nice if you add some minimal example for your code here

image

Server actions from as far as I understand and seen both in my own implementations and during mutliple events recently can be described like this. "Server actions are server-only functions you can pass to client components. When these functions are called they basically generate an rpc call towards your server which then responds with the return from your server action function."

This means that as far as I've seen and understand from the core concept of server action that they can only be called from the client, because they need to generate an rpc layer to function. If you try to call them from the server, you can basically call the same function without the "use server" directive because it gives you roughly the same result. Although in some instances the use server directive you actually prevent you from being able to use the given function from the server.

I want to give a disclaimer that the above is based on my personal experience and understanding of the given subject.

Hope this helped ya out.

nicitaacom commented 8 months ago

@nicitaacom Below you'll find a demo repo which shows you two ways of updating cookies from the server.

https://github.com/JesseKoldewijn/next-cookie-setter-repro

One implementation uses a server action to update the cookie and the other uses fetch to fire a http POST request to a route handler which updates the cookie that way.

Thank you for example

I checked it and I see you can set cookie using client components only Yes you can do it through server action or route handler but anyway you do it from client component

I thought that its possible to set cookie on server

Some code


export default async function RootLayout({ children }: { children: React.ReactNode }) {
  const ownerProducts = await getOwnerProducts()
  const ToastProvider = lazy(() => import("./providers/ToastProvider"))

  return (
    <html lang="en" className={getCookie("darkMode") ?? "dark"}>
      <body>
        <Layout>{children}</Layout>
        <ModalsQueryProvider ownerProducts={ownerProducts ?? []} />
        <ModalsProvider />
        <ToastProvider />
      </body>
    </html>
  )
}

The main reason for that was set theme on server so user see no white (400ms) then dark screen

I figure out it using this

    <html lang="en" className={getCookie("darkMode") ?? "dark"}>

Answer to my question

On server (in server components)

Next.js let you getCookie() Next.js don't let you setCookie()

On client (in client components)

Next.js let you getCookie() Next.js let you setCookie()

github-actions[bot] commented 8 months ago

This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.