Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Docs: Setting cookies in API Route Handlers isn't working at all. Unlike in middleware #52799

Closed
wkd-kapsule opened this issue Jul 17, 2023 · 19 comments
Labels
Documentation Related to Next.js' official documentation. locked

Comments

@wkd-kapsule
Copy link

wkd-kapsule commented Jul 17, 2023

What is the improvement or update you wish to see?

The current doc on how to set a cookie in the route handler using the app router is wrong. No matter what I try, I never get to set the cookie. Yet, the same method works in the middleware function.

Is there any context that might help us understand?

Help please

Does the docs page already exist? Please link to it.

https://nextjs.org/docs/app/building-your-application/routing/router-handlers#cookies

DX-1790

@wkd-kapsule wkd-kapsule added the Documentation Related to Next.js' official documentation. label Jul 17, 2023
@wkd-kapsule wkd-kapsule changed the title Docs: Setting cookies in API Route Handler Docs: Setting cookies in API Route Handlers isn't working at all. Unlike in middleware Jul 17, 2023
@asp3
Copy link

asp3 commented Jul 17, 2023

for me, setting cookies is working as expected, but when calling getting cookies from a route handler, there are no cookies (null)

ResponseCookies {
  _parsed: Map(0) {},
  _headers: HeadersList {
    cookies: null,
    [Symbol(headers map)]: Map(0) {},
    [Symbol(headers map sorted)]: []
  }
}

@corvallo
Copy link

corvallo commented Jul 17, 2023

I'm having the same issue.

Under /app/page.tsx, I have

export async function getConfig() {
  const configRes = await fetch('http://localhost:4200/api/config', {
    credentials: 'include', // cross-origin cookies
  });
  return await configRes.json();
}
export default async function Index() {
  const config = await getConfig();
  return <div>Index Page {JSON.stringify(config)}</div>;
}

Under /app/api/config/route.ts


  import { cookies } from 'next/headers';
  import { NextResponse } from 'next/server';
  export async function GET(req: Request) {
    // @ts-ignore
    cookies().set({
      name: 'cookieName',
      value: 'true',
      httpOnly: true,
    });
    return new NextResponse(
      JSON.stringify({
        t: 'test',
      }),
      { status: 200 },
    );
    //   const response = NextResponse.json(
    //     { data: 'config' },
    //     {
    //       status: 200,
    //     },
    //   );
  
    //   response.cookies.set('login', 'true', {
    //     path: '/',
    //     httpOnly: false,
    //     secure: true,
    //     maxAge: 60 * 60 * 24 * 7,
    //     sameSite: 'none',
    //   });
    //   return response;
}

I tried both commented and uncommented in route.ts, but none of this is working
Moreover if I try to set cookie via server actions it works correctly.

@wkd-kapsule
Copy link
Author

for me, setting cookies is working as expected, but when calling getting cookies from a route handler, there are no cookies (null)

ResponseCookies {
  _parsed: Map(0) {},
  _headers: HeadersList {
    cookies: null,
    [Symbol(headers map)]: Map(0) {},
    [Symbol(headers map sorted)]: []
  }
}

Would you please provide the code you used to set the cookie?

@wkd-kapsule
Copy link
Author

wkd-kapsule commented Jul 17, 2023

I'm having the same issue.

Under /app/page.tsx, I have

export async function getConfig() {
  const configRes = await fetch('http://localhost:4200/api/config', {
    credentials: 'include', // cross-origin cookies
  });
  return await configRes.json();
}
export default async function Index() {
  const config = await getConfig();
  return <div>Index Page {JSON.stringify(config)}</div>;
}

Under /app/api/config/route.ts


  import { cookies } from 'next/headers';
  import { NextResponse } from 'next/server';
  export async function GET(req: Request) {
    // @ts-ignore
    cookies().set({
      name: 'bet9ja',
      value: 'true',
      httpOnly: true,
    });
    return new NextResponse(
      JSON.stringify({
        t: 'test',
      }),
      { status: 200 },
    );
    //   const response = NextResponse.json(
    //     { data: 'config' },
    //     {
    //       status: 200,
    //     },
    //   );
  
    //   response.cookies.set('login', 'true', {
    //     path: '/',
    //     httpOnly: false,
    //     secure: true,
    //     maxAge: 60 * 60 * 24 * 7,
    //     sameSite: 'none',
    //   });
    //   return response;
}

I tried both commented and uncommented in route.ts, but none of this is working Moreover if I try to set cookie via server actions it works correctly.

I'm right there with you... I tried both and even setting a Set-Cookie header, none worked. Btw, I think that if you update Typescript to the latest version you won't need these @ts-ignore anymore.

@corvallo
Copy link

I noticed that the fetch response has set-cookie inside the headers

Response {
  [Symbol(realm)]: { settingsObject: {} },
  [Symbol(state)]: {
    aborted: false,
    rangeRequested: false,
    timingAllowPassed: false,
    requestIncludesCredentials: false,
    type: 'default',
    status: 200,
    timingInfo: null,
    cacheState: '',
    statusText: '',
    headersList: HeadersList {
      [Symbol(headers map)]: [Map],
      [Symbol(headers map sorted)]: null
    },
    urlList: [],
    body: { stream: undefined, source: [Uint8Array], length: 12 }
  },
  [Symbol(headers)]: HeadersList {
    [Symbol(headers map)]: Map(7) {
      'connection' => 'close',
      'content-encoding' => 'gzip',
      'content-type' => 'text/plain;charset=UTF-8',
      'date' => 'Tue, 18 Jul 2023 07:32:30 GMT',
      'set-cookie' => 'cookieName=true; Path=/; HttpOnly',
      'transfer-encoding' => 'chunked',
      'vary' => 'RSC, Next-Router-State-Tree, Next-Router-Prefetch, Accept-Encoding'
    },
    [Symbol(headers map sorted)]: null
  }
}

@wkd-kapsule
Copy link
Author

Yes, the set-cookie header is shown when I log the headers and yet, no cookie...

@corvallo
Copy link

I made some further tests.

I created another api route under pages folder pages/api/testapi.ts

import { NextApiRequest, NextApiResponse } from 'next';
import { serialize } from 'cookie';
export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const cookie = serialize('hello-cookie', 'api-hello-cookie-value', {
    path: '/',
  });
  res.setHeader('Access-Control-Expose-Headers', '*');
  res.setHeader('Access-Control-Allow-Origin', 'http://localhost:4200');
  res.setHeader('Access-Control-Allow-Credentials', 'true');
  //   res.setHeader('Set-Cookie', `token1=token1`);
  res.setHeader('Set-Cookie', cookie);
  res.status(200).json({ name: 'John Doe' });
}

I noticed that pointing the browser to http://localhost:4200/api/testapi, the cookie is correctly set and it is available to whole application.
But if use that api endpoint within a fetch inside a page

export async function getConfig() {
  const configRes = await fetch('http://localhost:4200/api/prova');
  console.log(configRes);
  return await configRes.json();
}
export default async function Index() {
  await getConfig();
  return <div>Index Page </div>;
}

it doesn't work

@corvallo
Copy link

I made an almost complete codesandbox here https://codesandbox.io/p/sandbox/silly-cookies-vhlvvn check it out.
I created a server action to show that the cookie is setted correctly while using server actions

@balazsorban44
Copy link
Member

Hi everyone, this is the expected behavior, but we should explain it better in the docs.

You cannot set cookies during render (inside Pages, Layouts, etc.). When a fetch request is made (eg. via a Route Handler), the Set-Cookie header won't be accounted for during the rendering. Route Handlers do support setting cookies which is documented here.

You can verify this by directly visiting an endpoint that uses Route Handlers. (Observation here #52799 (comment))

When using Server Actions, the recommendation is to import the logic of the Route Handler instead of doing a fetch call, since you can set cookies via cookies().set inside a Server Action. See: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions#using-headers

@wkd-kapsule
Copy link
Author

Hi everyone, this is the expected behavior, but we should explain it better in the docs.

You cannot set cookies during render (inside Pages, Layouts, etc.). When a fetch request is made (eg. via a Route Handler), the Set-Cookie header won't be accounted for during the rendering. Route Handlers do support setting cookies which is documented here.

You can verify this by directly visiting an endpoint that uses Route Handlers. (Observation here #52799 (comment))

When using Server Actions, the recommendation is to import the logic of the Route Handler instead of doing a fetch call, since you can set cookies via cookies().set inside a Server Action. See: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions#using-headers

Hi, thanks for the answer. Yet, it is still confusing to me.

The link you've provided to set a cookie in a Route Handler is using server action, which I'm not because it is still experimental. Other than that, how am I supposed to access/trigger that cookie function in the Route Handler if I can't fetch the Route? You talk about "directly visiting an endpoint". Am I supposed to redirect my users to a blank /api page just for the cookie to be set? This looks like a wobbly, barely effective workaround.

But I guess it doesn't even matter. Because my use case is involving a fetch request. During that fetch, depending on the data obtained, I want to set a corresponding cookie. Given what you said, Nextjs is intentionally built to make it impossible...

@wkd-kapsule
Copy link
Author

"You can set cookies using Route Handlers"
"The way to access a Route Handler is a fetch request"
"You can't set a cookie using a fetch request and it is an intended behavior"

Isn't it a bit conflicting or am I getting something wrong?

@rexfordessilfie
Copy link
Contributor

Hello @kapsule-studio. Not sure if this is already been suggested regarding the primary issue, but I think the issue is that when rendering your page, the fetch call you make inside of getConfig occurs on the server-side, where the browser cannot receive and store the cookies from the headers.

Some solutions you may try:

  1. The suggestion @balazsorban44 is making is to copy over similar logic as you have inside of your route handler into the server action, rather than making a fetch request. According to the docs, the cookies().set method should appropriately set the cookies to be returned to the browser and stored by the browser once the page is rendered. One thing I found trying this was to trigger the server action via a form submission.

  2. An alternate potential solution to try is to opt out of the server-side rendering into client-side rendering and use something such as a useEffect to make the fetch request client-side, this way the response object would make its way back to the browser where it can set the cookies appropriately. For this, you may have to use credentials: 'include' in the fetch request.

Both of these approaches are in this CodeSandbox example. You may have to run them locally to notice cookies being set. I did not see them getting set in the CodeSandbox environment.

@aalfiann
Copy link

aalfiann commented Aug 9, 2023

I've read the docs and tried many times but still fail, cookies still undefined..

How to get cookies with use client in page.tsx? The docs about cookies is not complete..

Update:
Finally I've made it, with using route and call it with

fetch('http://localhost:3000/api/session', { cache: 'no-store' })

@ARGONoid
Copy link

ARGONoid commented Aug 26, 2023

I keep getting this error
Error: Cookies can only be modified in a Server Action or Route Handler.

Is possible set cookie on server side?
My code

'use server'
 
import { cookies } from 'next/headers'
 
async function deleteCookie(name: string) {
  cookies().delete(name)
}

This function is called in the server component.
I can't set cookies on the client side because httpOnly is required for authorization.

@AayushKarki714
Copy link

Hi everyone, this is the expected behavior, but we should explain it better in the docs.

You cannot set cookies during render (inside Pages, Layouts, etc.). When a fetch request is made (eg. via a Route Handler), the Set-Cookie header won't be accounted for during the rendering. Route Handlers do support setting cookies which is documented here.

You can verify this by directly visiting an endpoint that uses Route Handlers. (Observation here #52799 (comment))

When using Server Actions, the recommendation is to import the logic of the Route Handler instead of doing a fetch call, since you can set cookies via cookies().set inside a Server Action. See: https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions#using-headers

Ohh, I got it, sending a request from client to server only sent the cookies, but making a api call during the render phase inside of server component won't send the cookies, since the client is the one who has the cookies, but in this case we are dealing with server to server

@AayushKarki714
Copy link

Is there anyway to make sure that cookies is sent even in the api call made inside server components??

@achievecreative
Copy link

I keep getting this error Error: Cookies can only be modified in a Server Action or Route Handler.

Is possible set cookie on server side? My code

'use server'
 
import { cookies } from 'next/headers'
 
async function deleteCookie(name: string) {
  cookies().delete(name)
}

This function is called in the server component. I can't set cookies on the client side because httpOnly is required for authorization.

I have same issue in 14.0.1, the doc said we can set the cookie in server action - https://nextjs.org/docs/app/building-your-application/data-fetching/forms-and-mutations#setting-cookies

@leerob
Copy link
Member

leerob commented Nov 9, 2023

You should be able to set cookies in both Server Actions and Route Handlers. The documentation is correct. If you are seeing an issue, please open a new issue with a reproduction so we can investigate your case. Thanks!

Copy link
Contributor

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.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 24, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Documentation Related to Next.js' official documentation. locked
Projects
None yet
Development

No branches or pull requests

10 participants