vercel / next.js

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

Setting Cookies Via Server Action Reloads Entire App #50163

Open johnson-jesse opened 1 year ago

johnson-jesse commented 1 year ago

Verify canary release

Provide environment information

Operating System:
      Platform: darwin
      Arch: arm64
      Version: Darwin Kernel Version 22.4.0: Mon Mar  6 20:59:28 PST 2023; root:xnu-8796.101.5~3/RELEASE_ARM64_T6000
    Binaries:
      Node: 16.14.0
      npm: 8.3.1
      Yarn: 1.22.19
      pnpm: 6.11.0
    Relevant packages:
      next: 13.4.4-canary.0
      eslint-config-next: 13.4.3
      react: 18.2.0
      react-dom: 18.2.0
      typescript: 5.0.4

Which area(s) of Next.js are affected? (leave empty if unsure)

App directory (appDir: true)

Link to the code that reproduces this issue or a replay of the bug

https://github.com/johnson-jesse/next-server-action-cookie

To Reproduce

  1. Clone repo, install, run dev.
  2. Click the first button labeled with "Via Server Action With Cookies"
  3. Notice the whole app re-renders.
  4. Optionally turn on "Highlight updates when components render." from developer window to see updates from button clicks.

Describe the Bug

Setting cookies in a server action causes the entire app to reload. This appears as a render glitch most noticeable when the layout is using a custom font that also needs to load.

https://github.com/vercel/next.js/assets/10335064/6dd72eca-93e5-460e-ab4f-38d6e9a73dd2

Expected Behavior

Setting cookies in a server action does not cause the entire app to reload thus avoiding loading the font again.

Which browser are you using? (if relevant)

Chrome Version 112.0.5615.137 (Official Build) (x86_64)

How are you deploying your application? (if relevant)

N/A

shuding commented 1 year ago

This appears as a render glitch most noticeable when the layout is using a custom font that also needs to load.

That's dev only as we ditch all the cache of CSS resources when a server refresh is triggered, which should be fine. Are you seeing that on prod as well?

Also, if cookies are mutated inside an Action, it's expected to re-render the app because there could be data that depends on cookies.

johnson-jesse commented 1 year ago

@shuding In prod, the glitch is only apparent when turning on highlighting. The prod example can be interacted with here.

https://github.com/vercel/next.js/assets/10335064/76eb8748-6e11-417b-9f70-734bc8b54135

The visual glitch is gone here, but, do we really want to assume the entire app should reload every time "any" cookie is set or mutated? In some cases, a cookie should change frequently, with every click.

J4v4Scr1pt commented 8 months ago

any updates one this? Facing the same issue.

Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

GuillaumeHalb commented 7 months ago

Same issue here. Setting cookie triggers db queries, thus I would like to avoid those reloads.

dance374 commented 7 months ago

Almost year has passed since you posted this and I'm experiencing same issue.

coderbahrom commented 6 months ago

The same issue with me how to handle it anyone can help me

Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

a-bugaj commented 5 months ago

I'm experiencing same issue.. will this be resolved somehow? setting a cookie using api route does not sound good.

zoolyka commented 5 months ago

I'm facing the same issue.

The server action triggers all database queries (used to server side render the page of the actual route) when a cookie is set or deleted, even in production. This seems totally useless, since the components are not even rendered on the server and not sent to the client.

Looking for an explanation why this happens, and potentionally how to avoid it wihout switching to an API route.

macdigger commented 5 months ago

I wonder to which degree using api route could be considered a good practice.

I mean if it is - so it will be, but on the other hand, I'm using server actions (useFormState basically) to process user login form, which I process with server action, set a cookie if login was a success, and return json response.

However, due to full reload, the return value never actually has a chance to reach its caller, because the whole app gets remounted (not even refreshed, but complete remount, including reset of all vars defined by useState). It feels like a really thermonuclear thing to me?…

jsabol commented 4 months ago

I'm experiencing this issue when running getSession inside of a server action, in combination with useFormState. I tried this with essentially a NOOP just executing getSession, and I am experiencing a full page refresh with all fonts/css reloaded, causing the message not displaying due to the refresh. Next.js is recommending this paradigm (useFormState) instead of API requests. If I strip out the getSession call, the page does not refresh and the message displays.

import { getSession } from "@auth0/nextjs-auth0";

export async function saveUsername(
  prevState: SetUsernameFormState,
  formData: FormData
): Promise<SetUsernameFormState> {
  const session = await getSession();
  return {
    message: "Nothing to do",
  };
}
comtef commented 1 month ago

I'm facing the same issue, using auth0 to check for authenticated user on server actions. It triggers a full reload of the page. I'd like to find a solution to avoid refactoring all the app to api routes...

magnusriga commented 1 month ago

Also, if cookies are mutated inside an Action, it's expected to re-render the app because there could be data that depends on cookies.

I tested this @shuding, and it seems that a Server Component "SC" does not actually re-render when a Server Action sets cookies after being called from a Client Component "CC" on the same route as the SC.

It seems it is necessary to call router.refresh() manually, in the CC, after the Server Action call.

Are others seeing that behavior as well?

EDIT:

From the docs it seems the Router Cache is indeed invalidated when cookies are changed in a Server Action, but a new request is not immediately sent to the server. The new request only happens upon navigation. To both invalidate the Router Cache, and send new request right away, use router.refresh().

nbxNTC commented 2 weeks ago

@magnusriga

Not sure if was a bug in the new version, but after upgrading to Next.js 15, updating the cookie in a Server Action called from a Client Component, is re-rendering the main Server Component.

  1. Server component (Do something and re-render after every cookie change)
  2. Client component (Call the server action)
  3. Server actions (Update cookie)
JuSfrei commented 3 days ago

+1

Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

bdrtsky commented 2 days ago

Issue still here in 15. I thought I am insane, before I found this topic. The least thing I would expect is for this to happen. Like a walking on mine field lol.

underovsky commented 2 days ago

Also run into this issue, this really messes the application flow as it's an unexpected behavior. I searched through Next.js docs and didn't find any mention of it.

Anyone found any workaround other than moving to API route?

underovsky commented 2 days ago

I found something semi-useful by analyzing headers.

It won't be helpful for cases when server action fetches data, but I personally found it useful to skip server component data validation for cookie-based data.

I noticed that upon the re-render of the server component via server action (that sets the cookie) Next.js is setting a new header called next-action. This header is not present upon the initial rendering.

Sample code:

import { cookies, headers } from "next/headers";

export default async function Page() {
  const isInitialRender = !headers().get("next-action");
  const cookie = cookies().get("some-cookie");

  // Sample validation triggered only on initial render
  if (isInitialRender && cookie && cookie.value !== "something-we-want") {
    throw new Error();
  }

  // Rest of the component
  // ...
}