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

[NEXT-1181] DynamicServerError: Dynamic server usage: cookies #49373

Closed
1 task done
focux opened this issue May 6, 2023 · 67 comments · Fixed by #53402 or koksing456/chatgenix#2
Closed
1 task done

[NEXT-1181] DynamicServerError: Dynamic server usage: cookies #49373

focux opened this issue May 6, 2023 · 67 comments · Fixed by #53402 or koksing456/chatgenix#2
Labels
area: app App directory (appDir: true) bug Issue was opened via the bug report template. linear: next Confirmed issue that is tracked by the Next.js team. locked

Comments

@focux
Copy link

focux commented May 6, 2023

Verify canary release

  • I verified that the issue exists in the latest Next.js 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.13.2
      npm: 9.3.0
      Yarn: 1.22.19
      pnpm: 7.25.1
    Relevant packages:
      next: 13.4.1
      eslint-config-next: 13.3.0
      react: 18.2.0
      react-dom: 18.2.0

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

App directory (appDir: true)

Link to the code that reproduces this issue

https://github.com/focux/next-cookies-bug

To Reproduce

  1. Clone the repository
  2. Run npm run build

Describe the Bug

After upgrading to Next from 13.3.5 to 13.4.1, I'm getting a lot of errors that say:

- info Creating an optimized production build ..node:internal/process/promises:246
          triggerUncaughtException(err, true /* fromPromise */);
          ^

DynamicServerError: Dynamic server usage: cookies
    at staticGenerationBailout (/Users/[redacted]/Projects/[redacted]/apps/web-app/.next/server/chunks/3311.js:46379:21)
    at cookies (/Users/[redacted]/Projects/[redacted]/apps/web-app/.next/server/chunks/6647.js:172:62)
    at getAccessToken (/Users/[redacted]/Projects/[redacted]/apps/web-app/.next/server/chunks/9041.js:30473:40)
    at /Users/[redacted]/Projects/[redacted]/apps/web-app/.next/server/chunks/9041.js:30505:55
    at /Users/[redacted]/Projects/[redacted]/apps/web-app/.next/server/chunks/6647.js:2598:44 {
  digest: 'DYNAMIC_SERVER_USAGE'
}

The app works good when running it on development mode.

Expected Behavior

According to the docs, using the cookies function automatically opt-ins my pages to dynamic rendering.

So I would expect the build to succeed as before and also those errors to be clearer to at least know in what page is originating.

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

NEXT-1181

@focux focux added the bug Issue was opened via the bug report template. label May 6, 2023
@github-actions github-actions bot added the area: app App directory (appDir: true) label May 6, 2023
@timneutkens timneutkens added the linear: next Confirmed issue that is tracked by the Next.js team. label May 17, 2023
@timneutkens timneutkens changed the title DynamicServerError: Dynamic server usage: cookies [NEXT-1181] DynamicServerError: Dynamic server usage: cookies May 17, 2023
@sinclairnick
Copy link

Am getting this issue too. @timneutkens do you have any guidance on workarounds prior to this being fixed? I couldn't find docs about how to use the export const dynamic stuff, which I imagine may help here.

@naseef0
Copy link

naseef0 commented May 25, 2023

I have a similar issue Error: Dynamic server usage: cookies

When I removed generateStaticParams from the layout, it no longer occurs.

But we need this during static page generation on the build, right?

@Ismet1999
Copy link

I solved this problem simply by adding this line to the app/layout.tsx file
export const dynamic = 'force-dynamic'

@naseef0
Copy link

naseef0 commented May 29, 2023

I solved this problem simply by adding this line to the app/layout.tsx file export const dynamic = 'force-dynamic'

Is that the correct way?

@weilueluo
Copy link

any update?

@Jordaneisenburger
Copy link

Running into the same issue, export const dynamic = 'force-dynamic' does "fix" the issue but it's not an ideal solution I think. Also seems like #49066 and #50634 and are related

@ifuller1
Copy link

Force dynamic prevents any static rendering. Static rendering was working for pages using "cookies" previously (at least, it appeared to be).

@martin-coded
Copy link

I had a similar problem with draftMode() and as a quick workaround I just added a try catch block to make sure that I can access the cookies. With this I can still generate the pages with generateStaticParams

function isDraftModeTrue(): boolean {
  try {
    const {isEnabled} = draftMode()
    return isEnabled;
  } catch (error) {
    return false;
  }
}
const isDraftMode: boolean = isDraftModeTrue();
if(isDraftMode){
 // fetch some data and set header with jummy cookie data
}

I think you could do the same only for cookies.

function isCookieAvailable(): boolean {
  try {
    const cookieStore = cookies();
    return true;
  } catch (error) {
    return false;
  }
}

I just started with next.js. I know it doesn't make sense to access cookies when the pages are getting pre-generated, but I also didnt't figure out a better way to solve this.

@SamKomesarook
Copy link

The export const dynamic = 'force-dynamic line worked for me when placed in my layout.tsx file, but when I navigate to a different page using the useRouter hook, I get the following err:

Error: Dynamic server usage: force-dynamic

@muhaimincs
Copy link
Contributor

any work around?

@optimizfx
Copy link

I'm getting this error too, I managed to squelch the error but it still didn't compile any api route pages :(

So far I've tried - export const dynamic = 'force-dynamic' in both page and layout files and in api routes

@vrazn
Copy link

vrazn commented Jun 15, 2023

Same problem here.
Have an app with i18n setup where the root layout (app/[lng]/layout.tsx) has the following code

export async function generateStaticParams() {
  return LANGUAGES.map((lng) => ({ lng }));
}

Any redirects to pages that either use searchParams or declare any of the dynamic route options causes the app to throw an error in development.

Removing the code from the layout.tsx fixes the problem, but makes all of the routes server-side-generated, which is not what I want.

Any suggestions on how to fix that?

@ariesclark
Copy link

The same happens for other "dynamic" opt-in functions like searchParams and headers.
Maybe rename this issue to reflect as such?

@brocchirodrigo
Copy link

This solution temporarily fixed the issue, but we need to remove after bug fix.
I'm also waiting for the fix.

I also noticed that the dynamicParams to False is also in trouble. This reflects the "fallback" used in ISR.

@nestorbfgo
Copy link

I'm getting this error too, I managed to squelch the error but it still didn't compile any api route pages :(

So far I've tried - export const dynamic = 'force-dynamic' in both page and layout files and in api routes

Im facing same here

@filipkowal
Copy link

It throws an error on the page with const dynamic = 'force-dynamic' if the root layout uses generateStaticParams.

If I remove the force-dynamic directive and use only searchParams on the same page, there's no error thrown, but the searchParams object is empty.
Only after a hard page refresh, the searchParams are updated with a correct value.

I'm using 13.4.7 and a downgrade to 13.4.6 and 13.4.3 did not help.

@danger-ahead
Copy link
Contributor

Facing this app router next 13.4.5: in route.ts

@DanielhCarranza
Copy link

I solved it by adding export const revalidate = 0; to the layout.tsx

@filipkowal
Copy link

@DanielhCarranza wouldn't it opt the layout out of the static generation?
If so, there's no point in using generateStaticParams there in the first place if I am not mistaken.

@danger-ahead
Copy link
Contributor

export const revalidate = 0;

facing this in a route. did add this as well as export const dynamic = "force-dynamic";

for now, it seems to work. but during build, the warning's still there. Any fix for this?

@nick4fake
Copy link

export const dynamic = 'force-dynamic';
export const revalidate = 0;

This helps with dev server on initial load, but any change to any file immediately shows this:

Unhandled Runtime Error
Error: Dynamic server usage: force-dynamic

@Jordaneisenburger
Copy link

@Jordaneisenburger ah great, thank you! Yes ok, it seems that way then. But I'm actually wondering whether such an approach even makes sense – if the page is statically generated, then by definition it cannot incorporate dynamic elements, right? Or perhaps I don't understand how this works, which is also possible 😅

You are correct as far as I understand either a page is static or dynamic. I used to think we can mix those components to get best of both worlds.

@sebpowell
Copy link

Thank you! That's good to know – that was my understanding too, but guess that's not right.

@ariesclark
Copy link

You are correct as far as I understand either a page is static or dynamic. I used to think we can mix those components to get best of both worlds.

As far as I'm aware, you can mix static and dynamic down to the layout level, ie, a layout could be static, with dynamically pages iirc. I may be wrong though.

@kien-ngo
Copy link

kien-ngo commented Jul 24, 2023

Just had this issue today after upgrading my package.json. Can confirm that exporing "force-dynamic" makes the issue go away
image

"@supabase/auth-helpers-nextjs": "^0.7.3",
"@supabase/supabase-js": "^2.27.0",
"next": "^13.4.12",

@dalechyn
Copy link

I was facing the same issue, but i eventually found out that the error only showed when the 'dynamic' page (in my case, because i am using the cookies) was rendered through the router (using the Link component from next-intl). When i simply replaced the Link with a normal "a" tag, the error message not longer shows.

I do have the same specific error case with next-intl.

@emrecoban
Copy link

emrecoban commented Jul 26, 2023

In my case, using the latest NextJS (13.4.9) and @supabase/auth-helpers-nextjs@0.7.3 my issue was related to the using supabase server component client on page routes that were not dynamic (i.e. /home, /settings instead of /task/[taskId]).

I thought including import { cookies } from 'next/headers'; on a page.tsx automagically opted into dynamic rendering it seems like during the next build process if the route isn't dynamic it may be trying to render it statically. shrug

I was able to workaround it by finding the pages that used the the supabase server component client and added a force-dynamic.

export const dynamic = 'force-dynamic';

In my case I wanted them to always be dynamic, so it worked well.

I added export const dynamic = "force-dynamic" to supabase-server.js, but it didn't work. Do I have to add all of pages?

If I add it to layout.js, it gives that error:

Please make sure that all your non-static routes export the following edge runtime route segment config:
export const runtime = 'edge';

If I convert the runtime to edge, it gives that error (only server components):

Invariant: Method expects to have requestAsyncStorage

@kien-ngo @wontondon

Issue detail: #45371 (comment)

import { cookies } from 'next/headers';
import { cache } from 'react';
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';

export const dynamic = "force-dynamic" //clodflare solution

export const createServerSupabaseClient = cache(() =>
    createServerComponentClient({ cookies })
)

export async function getSession() {
    const supabase = createServerSupabaseClient()
    try {
        const {
            data: { session }
        } = await supabase.auth.getSession()
        return session
    } catch (error) {
        console.error('Error:', error)
        return null
    }
}

@kien-ngo
Copy link

@emrecoban I only needed to add it to one single page (as seen in my code above)

@wontondon
Copy link

@emrecoban I added to all the pages I was experiencing the message.

@mbret
Copy link

mbret commented Jul 28, 2023

Interesting, I got the error while using cookies inside another function from my Page. If I make a call to cookies directly in the page component it's fine.

@emrecoban
Copy link

Interesting, I got the error while using cookies inside another function from my Page. If I make a call to cookies directly in the page component it's fine.

Interesting. I am using cookies with Supabase. It didn't work unfortunately (runtime is edge, in prod).

import { cookies } from 'next/headers';
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';

export default async function Dashboard() {
    const supabase = createServerComponentClient({ cookies })
    async function getSession() {
        try {
            const {
                data: { session }
            } = await supabase.auth.getSession()
            return session
        } catch (error) {
            console.error('Error:', error)
            return null
        }
    }
    const serverSession = await getSession()
    const userInfo = serverSession?.user
    return ()
}

Error message:

Error: Invariant: Method expects to have requestAsyncStorage, none available

@mbret
Copy link

mbret commented Jul 28, 2023

Interesting, I got the error while using cookies inside another function from my Page with my supabase call. If I make a call to cookies directly in the page component it's fine.

Interesting. I am using cookies with Supabase. It didn't work unfortunately (runtime is edge, in prod).

import { cookies } from 'next/headers';
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';

export default async function Dashboard() {
    const supabase = createServerComponentClient({ cookies })
    async function getSession() {
        try {
            const {
                data: { session }
            } = await supabase.auth.getSession()
            return session
        } catch (error) {
            console.error('Error:', error)
            return null
        }
    }
    const serverSession = await getSession()
    const userInfo = serverSession?.user
    return ()
}

Error message:

Error: Invariant: Method expects to have requestAsyncStorage, none available

That's exactly what I have right now, having cookies import directly in the page same as you and it works. Can you try to upgrade your dependencies ?

I can only extrapolate on what is going on here, nextjs build system is maybe trying to asserts things statically by analysing the code. By having cookies import in the page.tsx directly, it is analysed as valid.

@mbret
Copy link

mbret commented Jul 28, 2023

by the way my code was working correctly before, it has to be since a specific next version.

@emrecoban
Copy link

emrecoban commented Jul 28, 2023

@mbret thanks for the response. Everything is up to dated (#52176 (comment)). I guess cookies doesn't work in edge runtime, but it doesn't build my app if I change runtime to nodejs. It want me to make runtime edge. Interesting. I am really stuck, and Next.js team is silent.

@mbret
Copy link

mbret commented Jul 28, 2023

@mbret thanks for the response. Everything is up to dated (#52176 (comment)). I guess cookies doesn't work in edge runtime, but it doesn't build my app if I change runtime to nodejs. It want me to make runtime edge. Interesting. I am really stuck, and Next.js team is silent.

to be fair, app router is not stable yet, I decided to try a new project on it and I regret already. It's very early and hard to get help. My workaround in your case is to use the client component which work correctly instead. You will lose your SSR optimization but you can still retrieve your session on client side.

@timneutkens
Copy link
Member

timneutkens commented Jul 28, 2023

Hey all, I had a look into the reproduction provided on the initial issue post. It seems the reason that it doesn't work in the urql case is that it uses setTimeout / out of band promises quite a bit.

The reason you can call cookies() anywhere in React components / other functions called from React rendering is that we leverage Async Context. More precisely the implementation of the same concept that currently exists in Node.js which is called AsyncLocalStorage (don't get confused, this has nothing to do with the browser localstorage api). The way that Async Context works is that you can pass values within the same call stack, if setTimeout or such is used it'll break out of that call stack and the value can't be read, causing an error that say something about the context missing.

The case that you're running into is different though, it seems the authExchange is not bound to .query / .toPromise, which then causes an Uncaught Exception on the promise. The reason you see this as an error is that we leverage throwing to opt-out of static rendering during a build. When you call cookies() / headers() (or other APIs that opt out of static rendering) an error is thrown (Dynamic server usage: cookies), this error is caught by Next.js and will automatically mark the page as needing dynamic rendering. The problem here with urql is that because the function called is unbound a global rejection happens.

Changing the code to something like this will work:

import { cacheExchange, Client, createClient, fetchExchange } from "@urql/core";
import { authExchange } from "@urql/exchange-auth";
import { cookies } from "next/headers";

export const getAccessToken = async (cookie: string) => {
  const res = await fetch(`http://localhost:3000/api/auth/access-token`, {
    cache: "no-store",
    headers: {
      cookie,
    },
  });

  if (res.status === 401) {
    throw new Error("Forbidden");
  }

  return (await res.json()) as { accessToken: string };
};

export const getClient = () => {
  const cookie = cookies()
    .getAll()
    .map((c) => `${c.name}=${c.value}`)
    .join(";");
  return createClient({
    url: `https://graphql-pokeapi.graphcdn.app/`,
    fetchOptions: {
      cache: "no-store",
    },
    requestPolicy: "network-only",
    exchanges: [
      cacheExchange,
      authExchange(async (utils) => {
        const { accessToken: token } = await getAccessToken(cookie);

        return {
          addAuthToOperation: (operation) => {
            if (!token) {
              return operation;
            }

            return utils.appendHeaders(operation, {
              Authorization: `Bearer ${token}`,
            });
          },
          didAuthError(error) {
            return error.graphQLErrors.some(
              (e) => e.extensions?.code === "UNAUTHENTICATED"
            );
          },
          async refreshAuth() {
            const { accessToken: token } = await getAccessToken();
          },
        };
      }),
      fetchExchange,
    ],
  });
};

Codesandbox with the changes running the production build: https://k7q35k-3000.csb.app/
Code on codesandbox: https://codesandbox.io/p/github/focux/next-cookies-bug/csb-k7q35k/draft/peaceful-wescoff?file=/lib/urql.ts:21,27

However I think this is still a bug in urql as well so I'll dig a bit deeper into that.

The reason that adding force-dynamic worked for most people in this thread is that you then skip rendering during the build altogether as it's explicitly saying the page is dynamic.

I'll have a look into the Supabase case as well, seems like it's potentially similar to the urql case where something is not bound to the same call stack.

We'll also work on improving the error / guidance around why the error happens.

What would be super helpful is if you can provide a GitHub repository or Codesandbox when running into this. I'm expecting most people on this thread to be running into different issues that has the same outcome. I.e. I've seen cases where awaiting promises was forgotten and such that also cause this.

@emrecoban
Copy link

emrecoban commented Jul 28, 2023

The issue was solved through this way: #45371 (comment)

import { cookies } from 'next/headers';
import { cache } from 'react';
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';

export const createServerSupabaseClient = cache(() => {
    const cookieStore = cookies()
    return createServerComponentClient({ cookies: () => cookieStore })
})

export async function getSession() {
    const supabase = createServerSupabaseClient()
    try {
        const {
            data: { session }
        } = await supabase.auth.getSession()
        return session
    } catch (error) {
        console.error('Error:', error)
        return null
    }
}

@vishal-sarg
Copy link

vishal-sarg commented Jul 29, 2023

I am getting this same error while trying to use Supabase auth using Cookies in Next

DynamicServerError: Dynamic server usage: cookies
--
11:53:59.000 | at staticGenerationBailout (/vercel/path0/.next/server/chunks/841.js:158:21)
11:53:59.001 | at Object.cookies (/vercel/path0/.next/server/chunks/993.js:94:62)
11:53:59.001 | at NextServerComponentAuthStorageAdapter.getCookie (/vercel/path0/.next/server/chunks/374.js:203:42)
11:53:59.001 | at NextServerComponentAuthStorageAdapter.getItem (/vercel/path0/.next/server/chunks/374.js:572:28)
11:53:59.001 | at getItemAsync (/vercel/path0/.next/server/chunks/374.js:3734:33)
11:53:59.002 | at SupabaseAuthClient._recoverAndRefresh (/vercel/path0/.next/server/chunks/374.js:2641:69)
11:53:59.002 | at /vercel/path0/.next/server/chunks/374.js:1429:32
11:53:59.002 | at Object.__stack_guard___initialize__ (/vercel/path0/.next/server/chunks/374.js:3954:38)
11:53:59.002 | at stackGuard (/vercel/path0/.next/server/chunks/374.js:3961:38)
11:53:59.002 | at async /vercel/path0/.next/server/chunks/374.js:1399:66 {
11:53:59.003 | digest: 'DYNAMIC_SERVER_USAGE'

Here are my Supabase files for reference

middleware.js

import { createMiddlewareClient } from "@supabase/auth-helpers-nextjs";
import { NextResponse } from "next/server";

export async function middleware(req) {
	const res = NextResponse.next();
	const supabase = createMiddlewareClient({ req, res });
	await supabase.auth.getSession();
	return res;
}

route.js

import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

export async function GET(request) {
	const requestUrl = new URL(request.url);
	const code = requestUrl.searchParams.get("code");

	if (code) {
		const supabase = createRouteHandlerClient({ cookies });
		await supabase.auth.exchangeCodeForSession(code);
	}

	// URL to redirect to after sign in process completes
	return NextResponse.redirect(requestUrl.origin);
}

page.js

import { createServerComponentClient } from "@supabase/auth-helpers-nextjs";
import { redirect } from "next/navigation";
import { cookies } from "next/headers";

export default async function Home() {
	const supabase = createServerComponentClient({
		cookies,
	});

	const {
		data: { session },
	} = await supabase.auth.getSession();

	if (!session) {
		redirect("/signup");
	} else {
		redirect("/dashboard");
	}

	return <div></div>;
}

dlwocks31 added a commit to dlwocks31/emobridge that referenced this issue Jul 29, 2023
@arthureberledev
Copy link

Going from

export const createServerComponentClient = cache(() =>
  _createServerComponentClient<Database>({ cookies })
);

to

export const createServerComponentClient = cache(() => {
  const cookieStore = cookies();
  return _createServerComponentClient<Database>({ cookies: () => cookieStore });
});

fixed it for me.

@vishal-sarg
Copy link

Thanks @arthureberledev, this fixed it!

@mbret
Copy link

mbret commented Aug 2, 2023

Going from

export const createServerComponentClient = cache(() =>
  _createServerComponentClient<Database>({ cookies })
);

to

export const createServerComponentClient = cache(() => {
  const cookieStore = cookies();
  return _createServerComponentClient<Database>({ cookies: () => cookieStore });
});

fixed it for me.

that's also how I fixed it. well done

timneutkens pushed a commit that referenced this issue Aug 8, 2023
When using imports from `next/headers` in a layout or page,
`StaticGenerationBailout` will throw an error to indicate Next.js should
fallback to dynamic rendering. However, when async context is lost, this
error is uncaught and leads to a confusing error message at build time.

This attempts to improve DX surrounding this error by linking out to a
page that explains when it might happen. I've also tweaked
`StaticGenerationBailout` to always throw a fully descriptive reason as
opposed to just `DynamicServerError: Dynamic server usage: cookies`

Closes NEXT-1181
Fixes #49373

---------

Co-authored-by: Lee Robinson <me@leerob.io>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
@github-actions
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 Aug 22, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: app App directory (appDir: true) bug Issue was opened via the bug report template. linear: next Confirmed issue that is tracked by the Next.js team. locked
Projects
None yet