Repository: next.js
Issue: Set cookie doesn't work in Router handler nor server action
Labels: examples

### Verify canary release

- [X] I verified that the issue exists in the latest Next.js canary release

### Provide environment information

```bash
Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 23.4.0: Fri Mar 15 00:10:42 PDT 2024; root:xnu-10063.101.17~1/RELEASE_ARM64_T6000
  Available memory (MB): 32768
  Available CPU cores: 10
Binaries:
  Node: 20.8.1
  npm: 10.1.0
  Yarn: 1.22.22
  pnpm: 8.9.2
Relevant Packages:
  next: 14.2.2 // There is a newer version (14.2.11) available, upgrade recommended!
  eslint-config-next: 14.2.2
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.2.2
Next.js Config:
  output: N/A
 ⚠ There is a newer version (14.2.11) available, upgrade recommended!
   Please try the latest canary version (`npm install next@canary`) to confirm the issue still exists before creating a new issue.
   Read more - https://nextjs.org/docs/messages/opening-an-issue
```


### Which example does this report relate to?

none

### What browser are you using? (if relevant)

_No response_

### How are you deploying your application? (if relevant)

_No response_

### Describe the Bug

I am trying to set up an `auth` page and hit some errors while trying to set cookies from the server side, despite setting them in Server Action and Router Handler as the documentation stated. The idea is when the user visit the auth page, for example `http://localhost:3010/auth?redirectTo=/en&tokenCsrf=1234&tokenRefresh=1234&tokenAccess=1234`, the mock cookies are set to the browser during the SSR and redirect the user to the `redirectTo` page. 

#### Here is my attempt to use Router Handler

```
// app/auth.tsx

import { AuthPageComponentParams } from '@/components/AuthPage';
import { BasePageProps } from '@/types/page';
dimport { redirect } from 'next/navigation';

type AuthPageParams = {
  searchParams: {
      tokenAccess?: string;
  tokenRefresh?: string;
  tokenCsrf?: string;
redirectTo?: string;
};
};

export default async function AuthPage({ searchParams }: AuthPageParams) {
  console.log('AuthPage', searchParams);

  await fetch('http://localhost:3010/api/auth/mock', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      credentials: 'include'
    },
    body: JSON.stringify(searchParams)
  });

  redirect(searchParams.redirectTo || '/');
}
```

```
// app/api/auth/mock

import { CONFIG } from '@/config/config';
import { HTTP_STATUS } from '@/constants/http_status';
import { MOCK_AUTH_COOKIES } from '@helper/constants';
import { ResponseCookie } from 'next/dist/compiled/@edge-runtime/cookies';
import { cookies } from 'next/headers';
import { NextRequest, NextResponse } from 'next/server';

export type AuthCookies = {
  tokenAccess?: string;
  tokenRefresh?: string;
  tokenCsrf?: string;
};

export const mockAuthCookieConfig: Cookies.CookieAttributes = {
  domain: `.localhost`,
  path: '/',
  secure: true,
  sameSite: 'lax' // NOTE: To avoid URL for breaking when clicked from an email.
};

type Body = AuthCookies;

export async function POST(request: NextRequest) {
  const { tokenAccess, tokenRefresh, tokenCsrf }: Body = await request.json();

  try {
    const cookieStore = cookies();

    if (!CONFIG.auth.mock && CONFIG.environment !== 'development_local' && tokenAccess && tokenRefresh && tokenCsrf) {
      return;
    }

    if (tokenAccess && tokenRefresh && tokenCsrf) {
      console.log('Setting mock auth cookies in the browser...');
      const oneMonth = 24 * 60 * 60 * 30;

      const cookiePairs = {
        [MOCK_AUTH_COOKIES.MOCK_CSRF_TOKEN]: tokenCsrf,
        [MOCK_AUTH_COOKIES.MOCK_REFRESH_TOKEN]: tokenRefresh,
        [MOCK_AUTH_COOKIES.MOCK_ACCESS_TOKEN]: tokenAccess
      };

      Object.entries(cookiePairs).forEach(([k, v]) => {
        cookieStore.set(k, v, {
          ...mockAuthCookieConfig,
          expires: oneMonth
        } as ResponseCookie);
      });

      console.log('Mock auth cookies set successfully', {
        [MOCK_AUTH_COOKIES.MOCK_CSRF_TOKEN]: cookieStore.get(MOCK_AUTH_COOKIES.MOCK_CSRF_TOKEN),
        [MOCK_AUTH_COOKIES.MOCK_REFRESH_TOKEN]: cookieStore.get(MOCK_AUTH_COOKIES.MOCK_REFRESH_TOKEN),
        [MOCK_AUTH_COOKIES.MOCK_ACCESS_TOKEN]: cookieStore.get(MOCK_AUTH_COOKIES.MOCK_ACCESS_TOKEN)
      });
    }
  } catch (e) {
    console.error(e);
  }
  return NextResponse.json({ ok: true, status: HTTP_STATUS.SUCCESS });
}
```

The endpoint is called properly, in the server log, I see
```
Setting mock auth cookies in the browser...
[0] Mock auth cookies set successfully {
[0]   MOCK_CSRF_TOKEN: {
[0]     name: 'MOCK_CSRF_TOKEN',
[0]     value: '1234',
[0]     domain: '.localhost',
[0]     path: '/',
[0]     secure: true,
[0]     sameSite: 'lax',
[0]     expires: 1970-01-01T00:43:12.000Z
[0]   },
[0]   MOCK_REFRESH_TOKEN: {
[0]     name: 'MOCK_REFRESH_TOKEN',
[0]     value: '1234',
[0]     domain: '.localhost',
[0]     path: '/',
[0]     secure: true,
[0]     sameSite: 'lax',
[0]     expires: 1970-01-01T00:43:12.000Z
[0]   },
[0]   MOCK_ACCESS_TOKEN: {
[0]     name: 'MOCK_ACCESS_TOKEN',
[0]     value: '1234',
[0]     domain: '.localhost',
[0]     path: '/',
[0]     secure: true,
[0]     sameSite: 'lax',
[0]     expires: 1970-01-01T00:43:12.000Z
[0]   }
[0] }
```

but in the browser, nothing is set.


#### Attempt with Server action

```
// actions/auth.ts

'use server';

import { cookies } from 'next/headers';
import { AUTH_COOKIES, MOCK_AUTH_COOKIES } from '@helper/constants';
import { CONFIG } from '@/config/config';
import { getCsrfTokenFromSignedCookie } from '@/utilities/auth/auth';
import { ResponseCookie } from 'next/dist/compiled/@edge-runtime/cookies';


export type AuthCookies = {
  tokenAccess?: string;
  tokenRefresh?: string;
  tokenCsrf?: string;
};

const mockAuthCookieConfig: Cookies.CookieAttributes = {
  domain: `.localhost`,
  path: '/',
  secure: true,
  sameSite: 'lax' // NOTE: To avoid URL for breaking when clicked from an email.
};

/*
 * This function sets the mock auth cookies in the browser.
 * It should only be used for development_local environment.
 * */
export async function setMockAuthCookiesIfApplicable({ tokenAccess, tokenRefresh, tokenCsrf }: AuthCookies) {
  const cookieStore = cookies()

  if (!CONFIG.auth.mock || CONFIG.environment !== 'development_local') {
    return;
  }

  if (CONFIG.auth.mock && tokenAccess && tokenRefresh && tokenCsrf) {
    console.log('Setting mock auth cookies in the browser');
    const oneMonth = 24 * 60 * 60 * 30;

    const cookiePairs = {
      [MOCK_AUTH_COOKIES.MOCK_CSRF_TOKEN]: tokenCsrf,
      [MOCK_AUTH_COOKIES.MOCK_REFRESH_TOKEN]: tokenRefresh,
      [MOCK_AUTH_COOKIES.MOCK_ACCESS_TOKEN]: tokenAccess
    };

    Object.entries(cookiePairs).forEach(([k, v]) => {
      cookieStore.set(k, v, {
        ...mockAuthCookieConfig,
        expires: oneMonth,
      } as ResponseCookie);
    })
  }
}
```

```
// app/auth/page.tsx

export default async function AuthPage({ searchParams }: AuthPageParams) {

  await setMockAuthCookiesIfApplicable(searchParams);

  redirect(searchParams.redirectTo || '/');
}
```

Now, when visiting the URL, it just throw straight error
![image](https://github.com/user-attachments/assets/e2def1f0-4b65-44ad-a6c4-7d90dc9b7c0a)

What am I missing here?



### Expected Behavior

`cookie.set` should work as doc described.

### To Reproduce

The code to reproduce is included in the description. I have nothing special included in the `layout.tsx` file.