next.js: [NEXT-1181] DynamicServerError: Dynamic server usage: cookies

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

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Reactions: 39
  • Comments: 67 (5 by maintainers)

Commits related to this issue

Most upvoted comments

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

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

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.

Everywhere I was passing cookies on a server component was giving me this issue. It was mainly on the Supabase getSession() funciton that used createServerComponentClient() function. To fix, I added ‘use server’ to the getSession() function and was able to clear the issue.

Does anyone have an update on this issue? Is this expected behaviour of Next.js or something we can hope to change in the future?

Currently facing the same issue with Supabase when it consumes cookies from next/header in a server component.

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?

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.

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 https://github.com/vercel/next.js/issues/49066 and https://github.com/vercel/next.js/issues/50634 and are related

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>;
}

The issue was solved through this way: https://github.com/vercel/next.js/issues/45371#issuecomment-1655408690

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
    }
}

For statically pre-rendered pages I used, e.g.:

export const dynamic = 'force-static'
export const revalidate = 60

But this feels like a temporary solution. I don’t want to put “force-static” to every ISR page.

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. 🤷

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.

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

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.

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

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?

@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.

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

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",

same issue adding export const dynamic = "force-dynamic" to the layout fixed it but seems like a shitty work around

This would mean that your whole page will be dynamic and not statically generated at run/build time which is the whole point of generateStaticParams as an example lets say you have a e-commerce product page. We want that whole page to be statically generated but only the price should be dynamic based on the currently logged in customer. I believe that this is currently not possible with Next.js

Okay progress: Turns out export const dynamic = 'force-dynamic' is exactly the correct current workaround on pages & layouts.

My problem lies with that one server component (a navbar) that’s on every page. Since that force-dynamic line doesn’t work for components, it looks like I’ll be moving the db call to a client component within it and see if that helps

I’m facing the same issue, but none of the above solutions seem to be resolving it for me.

next@13.4.10 and @supabase/auth-helpers-nextjs@0.7.3

npm run build fails with Error: Call rentries were exceeded after printing the following error 17 times:

DynamicServerError: Dynamic server usage: cookies
--
at staticGenerationBailout (/vercel/path0/.next/server/chunks/399.js:81:21)
at Object.cookies (/vercel/path0/.next/server/chunks/763.js:16966:62)
at NextServerComponentAuthStorageAdapter.getCookie (/vercel/path0/.next/server/chunks/763.js:203:42)
at NextServerComponentAuthStorageAdapter.getItem (/vercel/path0/.next/server/chunks/763.js:572:28)
at getItemAsync (/vercel/path0/.next/server/chunks/763.js:3720:33)
at SupabaseAuthClient.__loadSession (/vercel/path0/.next/server/chunks/763.js:2098:71)
at async /vercel/path0/.next/server/chunks/763.js:2069:36
at async Object.__stack_guard___useSession__ (/vercel/path0/.next/server/chunks/763.js:3939:32)
at async stackGuard (/vercel/path0/.next/server/chunks/763.js:3941:12)
at async /vercel/path0/.next/server/chunks/763.js:2067:24 {
digest: 'DYNAMIC_SERVER_USAGE'

I have one server layout calling createServerComponentClient<Database>({ cookies }) as well as two server-rendered pages and one server-rendered component. The layout is shared by three more pages, and the component is on every page.

Every file that imports cookies from next/headers has been given export const dynamic = 'force-dynamic'; but without success.

I’ll update if I figure this out, other suggestions welcome!

For statically pre-rendered pages I used, e.g.:

export const dynamic = 'force-static'
export const revalidate = 60

But this feels like a temporary solution. I don’t want to put “force-static” to every ISR page.

A nice solution to this might be to have these automatically set as default options for pages that consume dynamic functions like cookies.

In case it helps someone, my problem with using cookies was about using generateStaticParams in layout.tsx. Using generateStaticParams prevents accessing cookies and headers since it generates paths at build time rather than runtime. Once I removed it, I was able to access cookies without any problem. Also note that revalidate and force-dynamic will be ignored if generateStaticParams defined.

Some extra info for others who have the same issue, I had to add export const dynamic = 'force-dynamic' to every page or layout that uses cookies / createServerComponentClient. The issue is also not 100% consistent for me, sometimes the build passes fine even without the force-dynamic which made this so hard to find.

Ideally though we would want to cache where we can and force-dynamic would prevent this, correct? Hopefully there will be a way to do this in future with caching!

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

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

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.

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.

@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.

@tatwater As far as I remember with my failed builds, it didn’t tell me which page was erroring during build. Is this the same on your end as well?

If so, would be great if Next.js told us what page was failing during build for DynamicServerError errors.

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.

My problem is solved by using this line in every API Route.js export const dynamic = 'force-dynamic';

Ex.

import { NextResponse } from “next/server”; import { cookies } from “next/headers”;

export const dynamic = “force-dynamic”; export async function POST(request, response) { …your code }

I had to add export const dynamic = 'force-dynamic' to every page or layout that uses cookies / createServerComponentClient.

In my case, the whole site is backend with Clerk-auth (which server method currentUser() uses cookies obviously) so every page (and the middleware) is doing that. Does the app dir setup not allow to set force-dynamic on the site level (in next.config.js)?

Also, as I understand it correctly, this is just a warning from Next.js build tools (like eg. when you are using experimental API’s). So I don’t really understand why this is throwing errors. In my case I don’t see the errors in build output as they are caught by Sentry.

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

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.

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?