Thanks to visit codestin.com
Credit goes to github.com

Skip to content

NextJS dynamicIO - ClerkProvider causing build failure when dynamic routes are used #4921

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

Open
4 tasks done
andrewkucz opened this issue Jan 17, 2025 · 11 comments
Open
4 tasks done
Assignees
Labels
needs-triage A ticket that needs to be triaged by a team member

Comments

@andrewkucz
Copy link

Preliminary Checks

Reproduction

https://github.com/andrewkucz/clerk-dynamic-io-issue

Publishable key

pk_test_c3VwcmVtZS1kb2xwaGluLTI2LmNsZXJrLmFjY291bnRzLmRldiQ

Description

Steps to reproduce:

  1. clone repro repo
  2. npm install
  3. npm run build

Expected behavior:

Successful build

Actual behavior:

Error: Route "/test-route/[dynamic]": A component accessed data, headers, params, searchParams, or a short-lived cache without a Suspense boundary nor a "use cache" above it. We don't have the exact line number added to error messages yet but you can see which component in the stack below. See more info: https://nextjs.org/docs/messages/next-prerender-missing-suspense
    at x (.next/server/chunks/782.js:6:53)
    at M (.next/server/chunks/782.js:6:794)
    at body (<anonymous>)
    at html (<anonymous>)
Error occurred prerendering page "/test-route/[dynamic]". Read more: https://nextjs.org/docs/messages/prerender-error
Export encountered an error on /test-route/[dynamic]/page: /test-route/[dynamic], exiting the build.

Hello,

I know dynamicIO is still in canary so I may be early and this may be low prio but couldn't find any reports of this and was frustrated so made a minimal repro to figure out the source lol. I am getting the above error when using dynamic routes with a ClerkProvider despite adding loading.tsx's everywhere and many different combinations of "use cache;", cacheLifes, and Suspense.

I was able to narrow down the issue to the combination of the use of ClerkProvider with the use of a dynamic route (in this case src/app/test-route/[dynamic]/page.tsx). Having both seems to cause the build to fail in the way above, even if auth() is not called anywhere and the middleware is not in place (the issue still occurs when both are present)

If you delete the ClerkProvider from the main layout.tsx file, the build will work, OR if you delete the src/app/test-route/[dynamic] folder so that there is no dynamic routes then the build will also pass. I also tried the dynamic prop to no avail.

Thanks!

Environment

System:
    OS: macOS 15.2
    CPU: (8) arm64 Apple M1 Pro
    Memory: 1.23 GB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 20.18.1 - ~/.nvm/versions/node/v20.18.1/bin/node
    Yarn: 1.22.19 - /opt/homebrew/bin/yarn
    npm: 10.8.2 - ~/.nvm/versions/node/v20.18.1/bin/npm
    pnpm: 8.9.0 - /opt/homebrew/bin/pnpm
    bun: 1.1.43 - ~/.bun/bin/bun
    Watchman: 2024.03.25.00 - /opt/homebrew/bin/watchman
  Browsers:
    Chrome: 131.0.6778.265
    Safari: 18.2
  npmPackages:
    @clerk/nextjs: ^6.9.12 => 6.9.12 
    @types/node: ^20 => 20.17.14 
    @types/react: ^19 => 19.0.7 
    @types/react-dom: ^19 => 19.0.3 
    next: ^15.2.0-canary.13 => 15.2.0-canary.13 
    react: ^19.0.0 => 19.0.0 
    react-dom: ^19.0.0 => 19.0.0 
    typescript: ^5 => 5.7.3
@andrewkucz andrewkucz added the needs-triage A ticket that needs to be triaged by a team member label Jan 17, 2025
@panteliselef panteliselef self-assigned this Jan 19, 2025
@panteliselef
Copy link
Member

Hey @andrewkucz thanks for reaching out. Supporting experimental dynamicIO is on our radar. Sounds like all you have to do is for now is to wrap <ClerkProvider/> with a Suspense. In practise you should never see its fallback as long as you don't use the dynamic props like <ClerkProvider dynamic/>.

More context: <ClerkProvider/> contains calls to "async" Node APIs but in this case you are not affected. You can test this out, by creating a full static route and you will see that the it remains static after the build.

@andrewkucz
Copy link
Author

thanks @panteliselef - this does resolve the build problem!

I thought adding a top level loading.tsx was the same as wrapping in Suspense but I guess I misunderstood some of Next's intricacies haha

Appreciate it though this will definitely unblock me for now.

@SennaSanzo
Copy link

I'm a newcomer to Next.js and Clerk, so I have a few questions on this particular topic. Perhaps I've misunderstood how dynamic I/O and the overall Next.js architecture work. Since we typically wrap the entire application in the root layout using the ClerkProvider, wouldn't placing everything within a Suspense component make the entire application dynamic? I'm also curious about the future impact, especially when Next.js releases the stable version of its new caching system.

@panteliselef
Copy link
Member

Hello @SennaSanzo, dynamicIO is quite fresh and we are experimenting with it internally. Having said that @clerk/nextjs is working as expected when the dynamicIO flag is turned on. Since the feature is still experimental we cannot ensure that something will not change or break in the future, which is way you will not find it in our official documentation.

wouldn't placing everything within a Suspense component make the entire application dynamic?
My understanding is that Suspense allows you to write dynamic code as children, while keeping parent components static. What actually opts a section of your application into dynamic rendering is accessing any async Node API, such as databases or timers.

With the launch of this feature, we had to put some async logic when your application is running on development mode. This is why recommend you do the following.

// This will not delay TTB
<Suspense>
  <ClerkProvider>{}</ClerkProvider>
</Suspense>

In practise, if you have already set your keys inside .env the async logic will be skipped and you will never see the Next.js error that prompts you to use "use cache" or suspense.

On the other hand if you explicitly use the dynamic prop then you definitely need to wrap with Supsense since that will access headers() which will then opt every into dynamic rendering.

// This will delay TTB, as it makes the code dynamic
<Suspense>
  <ClerkProvider dynamic>{}</ClerkProvider>
</Suspense>

@SennaSanzo
Copy link

Thank you so much for your response and clarification. The strange thing is that even though I'm already using the Clerk API keys and have registered them in my .env file, Next.js still complains that the ClerkProvider is trying to access asynchronous resources. Additionally, the project I'm working on includes next-intl for localization and uses dynamic routing (e.g. /[locale]/rest-of-the-route). Therefore, I'm not sure which part of the code is triggering the Next.js error. However, since this is an experimental feature, I'll have to stick with the approach for now and wait to see what happens in the near future.

For context here is what i'm doing in my root layout :

export default async function RootLayout({
  children,
}:
Readonly<{
  children: React.ReactNode;
}>) {
  const locale = await getLocale();

  return (
    //Needs to be there if not I got Next.js complaining about the Suspense or use cache stuff
    <Suspense>
      <ClerkProvider signInForceRedirectUrl={`/${locale}/member`} signUpForceRedirectUrl={`/${locale}/member`}>
        <html lang={locale}>
          <body className="antialiased relative landing-bg text-sm">
            <main>
              <NextIntlClientProvider>{children}</NextIntlClientProvider>
            </main>
          </body>
        </html>
      </ClerkProvider>
    </Suspense>
  );
}

And here is the the getLocale() function using the unstable_rootParams feature normally allowing to get the root params when dynamicIO is enabled.

import { Locale, routing } from "@/i18n/routing";
import { unstable_rootParams as rootParams } from "next/server";

export async function getLocale() {
  const { locale } = await rootParams();
  console.log("`locale` from `rootParams`: ", locale);

  if (!isLocale(locale)) {
    console.log(`Using default locale '${routing.defaultLocale}' as fallback`);
    return routing.defaultLocale;
  }

  return locale;
}

function isLocale(locale: unknown): locale is Locale {
  return typeof locale === "string" && routing.locales.includes(locale as Locale);
}

If you have any clue about why I'm still getting the Next.js error about use cache or suspense tag it would be very helpful. Thanks !

@bepstein9893
Copy link

Was wondering if any one looked into this issue. Using clerk/nextjs v6.12.11-canary.v20250326115904 and nextjs v15.3.0-canary.23 the only way to avoid the A component accessed data, headers, params, searchParams, or a short-lived cache without a Suspense boundary nor a "use cache" above it. We don't have the exact line number added to error messages yet but you can see which component in the stack below. See more info: https://nextjs.org/docs/messages/next-prerender-missing-suspense issue is to wrap the entire ClerkProvider and it's children in a Suspense, which seems to make the entire app dynamic even without specifying "dynamic" in the ClerkProvider.

Additionally, even if my ClerkProvider does not wrap the children in my root layout, it is having the effect of making all pages dyanmic rather than static.

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased flex h-screen w-screen`}
      >
        <Suspense fallback={<Skeleton />}>
          <ClerkProvider>
            <div />
          </ClerkProvider>
        </Suspense>
        <NuqsAdapter>{children}</NuqsAdapter>
      </body>
    </html>
  );
}

Thanks in advance for your help!

@panteliselef
Copy link
Member

@bepstein9893 can you create a minimal reproduction and share the repo url with us ?

@bepstein9893
Copy link

bepstein9893 commented Mar 26, 2025

Thanks @panteliselef

Please see this example repo here: clerk-nextjs-err-example

At a high level, it appears that the presence of ClerkProvider in a Next15 app using dynamicIO results in dynamic pages, even without wrapping the children in the ClerkProvider or providing ClerkProvider with the dynamic = {true}.

It's very possible I am doing something wrong so I appreciate you looking into this.

Using the code below, we see that the home page renders dynamically, though it is not wrapped in the clerk provider.
Image

Image

If we simply comment out the clerk provider, we see that the home page renders statically.
Image

Image

@panteliselef
Copy link
Member

panteliselef commented Mar 26, 2025

@bepstein9893 I understand the confusion here. If you run next build you should see something like this (see photo).
Image

Once you build you can clearly see that the / route is static, instead of dynamic. <ClerkProvider/> does a few more things in development in order to offer a smooth onboarding experience. Having said that we will try to improve this.

@bepstein9893
Copy link

Thanks so much @panteliselef! And is it okay not to wrap your app in the ClerkProvider? I prefer not to wrap the entire app in a Suspense if unnecessary.

@panteliselef
Copy link
Member

@bepstein9893 You should be able to remove suspense if it does not give you trouble for development. For production is definitely a noop if dynamic is not present.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-triage A ticket that needs to be triaged by a team member
Projects
None yet
Development

No branches or pull requests

4 participants