Next.js Discord

Discord Forum

Prerender error using `searchParams` with `Suspense`.

Answered
Dan6erbond posted this in #help-forum
Open in Discord
I'm trying to use dynamicIO in my app and made sure to update all my components to use a Suspense boundary when accessing data or searchParams, however, my [blog page](https://github.com/Dan6erbond/portfolio/blob/main/src/app/(frontend)/blog/page.tsx) throws a build error that I can't seem to figure out.

Even when I completely remove searchParams from the props and shrink down the page to the absolute minimum, Next.js will throw this error:

Error: Route "/blog": 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 O (.next/server/chunks/8711.js:1:14512)
    at c (.next/server/chunks/8711.js:1:24207)
    at f (.next/server/chunks/8711.js:1:24289)
    at l (.next/server/chunks/8711.js:1:10322)
    at c (.next/server/chunks/8711.js:1:10423)
    at R (.next/server/chunks/8711.js:1:15650)
    at y (.next/server/chunks/8711.js:1:7407)
    at v (.next/server/chunks/8711.js:1:13216)
    at E (.next/server/chunks/8711.js:1:14205)
    at u (.next/server/chunks/8711.js:1:27882)
    at P (.next/server/chunks/8711.js:1:15937)
Error occurred prerendering page "/blog". Read more: https://nextjs.org/docs/messages/prerender-error
Export encountered an error on /(frontend)/blog/page: /blog, exiting the build.


This leads me to believe that the error might not even be in the /blog route, but I can't figure out where else it might be because the rest of the app is really simple.
Answered by Dan6erbond
Hallelujah, thanks to the Next.js 15.2.0-canary.26 I can enable the useCache experiment instead of dynamicIO which gets rid of these errors!
View full answer

84 Replies

Have you tried adding a global loading.tsx file?
DynamicIO is still experimental and since the API is not stable and finishes you might encounter weird behaviors
since the blog page is getting the searchParams from props it might be enough to mark that component (the whole page) as dynamic, even if you're "unwrapping" the promise in children components, that's my guess
@LuisLl Have you tried adding a global loading.tsx file?
Does a loading.tsx forego the need for Suspense aka can I then await directly in the page.tsx or is it just an additional requirement? I wouldn't mind adding a blog/loading.tsx instead of all those Suspense wrappers.
blog/loading.tsx

is the same as
<Suspense fallback={your loading.tsx component}><BlogPage/></Suspense>

You can know await directly in page.tsx and the fallback will trigger
Oh nice!
Thanks for that. ^^
Let me try.
If you add the loading.tsx this is effectively a Suspense boundary that wraps your whole page, yuu can also wrap each component in its own Suspense to make the streaming more granular
If you leave only the loading.tsx the whole page will get suspended until each individual children resolved their own

const sp = await searchParams.

if youre doing that in every child, you could rather do it once on the parent and just pass the data, not the promise
Yeah, I'm going to try and wrap the whole page.tsx with a loading.tsx since for me wrapping each individual component is overkill due to a lot of shared data and the fact that the page is pretty much useless without posts anyway. I hope this fixes the prerendering issue!
Let me know how it goes
Ugh, damn, looks like that didn't do the trick. I have a blog/loading.tsx but still getting the same error:
Linting and checking validity of types
 ✓ Collecting page data
Error: Route "/blog": 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 O (C:\Users\morav\Documents\Projects\portfolio\.next\server\chunks\8711.js:1:14512)
    at c (C:\Users\morav\Documents\Projects\portfolio\.next\server\chunks\8711.js:1:24207)
    at f (C:\Users\morav\Documents\Projects\portfolio\.next\server\chunks\8711.js:1:24289)
    at l (C:\Users\morav\Documents\Projects\portfolio\.next\server\chunks\8711.js:1:10322)
    at c (C:\Users\morav\Documents\Projects\portfolio\.next\server\chunks\8711.js:1:10423)
    at P (C:\Users\morav\Documents\Projects\portfolio\.next\server\chunks\8711.js:1:15650)
    at y (C:\Users\morav\Documents\Projects\portfolio\.next\server\chunks\8711.js:1:7407)
    at v (C:\Users\morav\Documents\Projects\portfolio\.next\server\chunks\8711.js:1:13216)
    at E (C:\Users\morav\Documents\Projects\portfolio\.next\server\chunks\8711.js:1:14205)
    at u (C:\Users\morav\Documents\Projects\portfolio\.next\server\chunks\8711.js:1:27882)
    at R (C:\Users\morav\Documents\Projects\portfolio\.next\server\chunks\8711.js:1:15937)
Error occurred prerendering page "/blog". Read more: https://nextjs.org/docs/messages/prerender-error
Export encountered an error on /(frontend)/blog/page: /blog, exiting the build.
 ⨯ Static worker exited with code: 1 and signal: null
 ELIFECYCLE  Command failed with exit code 1.
When I disable the page I get this error that's even more weird IMO:
Error occurred prerendering page "/admin/[[...segments]]". Read more: https://nextjs.org/docs/messages/prerender-error
Error: Route "/admin/[[...segments]]" has a `generateMetadata` that depends on Request data (`cookies()`, etc...) or external data (`fetch(...)`, etc...) but the rest of the route was static or only used cached data (`"use cache"`). If you expected this route to be prerenderable update your `generateMetadata` to not use Request data and only use cached external data. Otherwise, add `await connection()` somewhere within this route to indicate explicitly it should not be prerendered.
Export encountered an error on /(payload)/admin/[[...segments]]/page: /admin/[[...segments]], exiting the build.
 ⨯ Static worker exited with code: 1 and signal: null
what if the problem isn't the blog/page.tsx
and it's the blog/[slug]/page.tsx
But since it's Payload and they aren't on the canary this might just be my fault.
@LuisLl what if the problem isn't the blog/page.tsx and it's the blog/[slug]/page.tsx
I was honestly thinking the same thing. I managed to clean up blog/[slug]/page.tsx and got rid of the errors it was throwing there.
It's obviously possible that there are still errors, but I wouldn't know what. 🤷🏽‍♂️
copy the loading.tsx from the /blog/ and paste it on /glob/[slug]/
Yes basically when you try the new experimental stuff you're on your own
No docs for that
Hahaha that's fine in this case, just a side project but I'll keep in mind to hold off on dynamic IO until it's released. 😂
@LuisLl copy the loading.tsx from the /blog/ and paste it on /glob/[slug]/
/blog/[slug] has its own loading.tsx:
import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbList,
  BreadcrumbSeparator,
} from '../../../../components/ui/breadcrumb'

import ScrollProgress from '../../../../components/ui/scroll-progress'
import { Skeleton } from '../../../../components/ui/skeleton'
import { cn } from '../../../../lib/utils'

export default function Loading() {
  return (
    <div className={cn('container', 'mx-auto', 'flex', 'flex-col', 'gap-4')}>
      <ScrollProgress className="top-[65px]" />
      <Breadcrumb>
        <BreadcrumbList>
          <BreadcrumbItem>
            <Skeleton className={cn('h-6', 'w-12')} />
          </BreadcrumbItem>
          <BreadcrumbSeparator />
          <BreadcrumbItem>
            <Skeleton className={cn('h-6', 'w-24')} />
          </BreadcrumbItem>
        </BreadcrumbList>
      </Breadcrumb>
    </div>
  )
}
Yup, for /blog not the specific page (luckily/unfortunately).
mmmh weird... i mean this is just speculation but what if that error is cached from before ?
You could delete the .next folder
it'll regenerate when you run the app
Tried that too. ^^
Unfortunately it wasn't that.
Was hoping it was something like that haha.
Oh lol 😭😭😭😭
Alright, I've disabled all pages but the homepage that only uses client components/cached data fetchers, and the blog page (slug, not overview) and it built it.
So now I'll try re-enabling /blog and see what happens.
Same error. Jeeze.
So the issue is in /blog confirmed.
I pushed all the changes and experiments I've made to the repo, so all the new loading UI, etc. but yeah, still not building.
Wait can I ask something, do you do this for some particular reason?
<div className={cn('flex', 'gap-2', 'justify-center', 'flex-wrap', 'max-w-6xl', 'self-center')} >

why not just
<div className='flex gap-2 justify-center flex-wrap max-w-6xl self-center' >
I try to keep it consistent with cn and I like how it wraps classes once I exceed the line length but it doesn't cause issues in other pages/components so do you think it's related to this error?
No, it has nothing to do with the error, i just noticed that pattern lol
Haha, yeah, for example my footer benefits from the wrapping I mentioned:
    <footer
      className={cn(
        'h-[80px]',
        'shadow',
        'bg-slate-950',
        'bg-gradient-to-r',
        'from-slate-950',
        'from-70%',
        'to-rose-950',
        'flex',
        'items-center',
        'justify-center',
        'mt-8',
      )}
    >
      <div className={cn('flex', 'gap-2', 'items-center')}>
        <span>© Copyright</span>
        <Dot />
        <span>RaviAnand Mohabir, 2025</span>
      </div>
    </footer>
Also, devtools show suspense is working on the blog page:
what is the latest error that you're getting?
In dev mode this:
Error: Route "/blog": 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 Layout [Server] (<anonymous>)
    at InnerLayoutRouter (c:\Users\morav\src\client\components\layout-router.tsx:317:2)
    at RedirectErrorBoundary (c:\Users\morav\src\client\components\redirect-boundary.tsx:43:4)
    at RedirectBoundary (c:\Users\morav\src\client\components\redirect-boundary.tsx:74:35)
    at HTTPAccessFallbackErrorBoundary (c:\Users\src\client\components\http-access-fallback\error-boundary.tsx:49:4)
    at HTTPAccessFallbackBoundary (c:\Users\src\client\components\http-access-fallback\error-boundary.tsx:154:2)
    at LoadingBoundary (c:\Users\morav\src\client\components\layout-router.tsx:423:2)
    at ErrorBoundary (c:\Users\morav\src\client\components\error-boundary.tsx:183:2)
    at InnerScrollAndFocusHandler (c:\Users\morav\src\client\components\layout-router.tsx:175:0)
    at ScrollAndFocusHandler (c:\Users\morav\src\client\components\layout-router.tsx:292:2)
    at RenderFromTemplateContext (c:\Users\morav\src\client\components\render-from-template-context.tsx:7:30)
    at OuterLayoutRouter (c:\Users\morav\src\client\components\layout-router.tsx:476:2)
Might be more helpful.
But I also don't get what it's saying.
This is weird my impostor syndrome is at the max rn, feels like i don't know anything about next.js lol
Oof.
I think I just found it
Layout has:
const contactPromise = (await getPayload()).findGlobal({ slug: 'contact' })
@LuisLl This is weird my impostor syndrome is at the max rn, feels like i don't know anything about next.js lol
I feel you - I was going to see if Next.js had stabilized the cache function that was released a couple months back and got hit with the new "use cache" directive and now I'm stuck here. 😂
I saw that earlier but layout wraps every other route too, doesnt it?
oh you just have one route
I have three, but maybe Next.js is just randomly hitting a route and throwing that error?
Also, I just realized that might not be it. Because I'm passing contactPromise to Navbar which is internally suspending the GetInTouch:
            <PopoverContent>
              <Suspense
                fallback={
                  <div className={cn('flex', 'flex-col', 'gap-2')}>
                    <div className={cn('flex', 'gap-2')}>
                      <Skeleton className="w-6 h-6 rounded-full" />
                      <Skeleton className="flex-grow h-6 rounded-full" />
                    </div>
                    <div className={cn('flex', 'gap-2')}>
                      <Skeleton className="w-6 h-6 rounded-full" />
                      <Skeleton className="flex-grow h-6 rounded-full" />
                    </div>
                    <div className={cn('flex', 'gap-2')}>
                      <Skeleton className="w-6 h-6 rounded-full" />
                      <Skeleton className="flex-grow h-6 rounded-full" />
                    </div>
                  </div>
                }
              >
                <GetInTouch contactPromise={contactPromise} />
              </Suspense>
            </PopoverContent>
but that specific layout only wraps blog/
Shouldn't it wrap (frontend)/page.tsx too?
Well, I mean, it does.
yes that too
And there it works fine...?
I'm so confused ngl
Wow, that's nasty. I cached getContact() and now it works (minus Payload but that's fine):
async function getContact() {
  'use cache'
  cacheTag('contact')

  return await (await getPayload()).findGlobal({ slug: 'contact' })
}

So it throws the error on a random page which makes the search a proper search for a needle in a haystack. 😂
Now I need to see if I can do something about Payload.
That error doesn't make much sense either:
Error occurred prerendering page "/admin/[[...segments]]". Read more: https://nextjs.org/docs/messages/prerender-error
Error: Route "/admin/[[...segments]]" has a `generateMetadata` that depends on Request data (`cookies()`, etc...) or external data (`fetch(...)`, etc...) but the rest of the route was static or only used cached data (`"use cache"`). If you expected this route to be prerenderable update your `generateMetadata` to not use Request data and only use cached external data. Otherwise, add `await connection()` somewhere within this route to indicate explicitly it should not be prerendered.
Export encountered an error on /(payload)/admin/[[...segments]]/page: /admin/[[...segments]], exiting the build.
 ⨯ Static worker exited with code: 1 and signal: null
I've never used payload, no idea what's happening there
Haha, no worries, appreciate your help! Hopefully dynamicIO stabilizes soon enough that I don't have to work around this issue with Payload. I might just disable it on the deployed app and run it locally to edit my content. 😂
Looks like they're aware of the issue and [there is a hack](https://github.com/payloadcms/payload/issues/8897).
LOL at least you solved the /blog part
Rose-breasted Grosbeak
any idea when dynamicIO will be stable? I want to start a new project, and I'd feel annoyed if it becomes stable during the development
And by the time it’s stable something else will come up and the story will repeat itself lol
@Rose-breasted Grosbeak Definitely wait for stable. While I've sorta been able to use it it's always some other issue and I can't upgrade to the latest canaries because they break the build on my currently working site. 🫠
@LuisLl yeah I'm kind of afraid of that considering what happened with the unstable_cache() function. 😂
@Dan6erbond <@744377057139753020> yeah I'm kind of afraid of that considering what happened with the `unstable_cache()` function. 😂
It’s still unstable even when another unstable solution is being more actively developed lol. Will forever been unstable until it’s deprecated
To be fair [the docs](https://nextjs.org/docs/app/api-reference/functions/unstable_cache) mention it will be replaced. 😂

Note: This API will be replaced by [use cache](https://nextjs.org/docs/app/api-reference/directives/use-cache) when it reaches stability.
@Dan6erbond To be fair [the docs](https://nextjs.org/docs/app/api-reference/functions/unstable_cache) mention it will be replaced. 😂 > **Note:** This API will be replaced by [`use cache`](https://nextjs.org/docs/app/api-reference/directives/use-cache) when it reaches stability.
Yes but that’s new haha it’s been unstable for so long until they decided it was not the right path and built a new solution to solve the caching problems unstable_cache was trying to and never succeeded 😭

I mean “use cache” fits next.js better, IMO
Answer
I don’t think you’re supposed to use “export const dynamic” with “use client”.

Also that useEffect that runs every time searchParams change to get the latest value of “data” isn’t needed, the component re-renders when searchParams change, you can do this in the body of the component:

const searchParams = useSearchParams();

const data = searchParams.get(“data”);
And suspense boundaries work if they’re places up on the tree not inside the same component that’s gonna suspend. I would recommend you to keep your page a Server Component, so you remove the “use client” at the top, move that to another file that exports your Client component, and when you import your Client component into the Page (which is a Server component) wrap it in <Suspense>
@Philippine Crocodile
If you want your full route to be dynamic keep the export const dynamic = “force-dynamic”;.
If you leave this you won’t need to wrap the Client component using useSearchParams in a <Suspense>, this is because useSearchParams will be available on the server during the initial server render.

If you remove the force-dynamic thing make sure to wrap your Client component in <Suspense> to allow for anything that’s not dependent on SearchParams to pre-render on the server.

Check this out for more info:
https://nextjs.org/docs/app/api-reference/functions/use-search-params#behavior
@Philippine Crocodile yes it works. thanks so much!
You're welcome, glad I could help! :p
Philippine Crocodile
how do i mark the solution?
@Philippine Crocodile how do i mark the solution?
My bad, you can't since you're not the Original Poster 😭