Next.js Discord

Discord Forum

searchParams object is empty while having dynamic = "force-dynamic" in page

Answered
Russian Toy posted this in #help-forum
Open in Discord
Russian ToyOP
Hi,
I enforced a page to be dynamically rendered but i noticed the searchParams is not giving any data on production. The type is from the doc : searchParams: Promise<{ [key: string]: string | string[] | undefined }>.

I noticed also I have the searchParams data in the generateMetadata function, but not in my async page.... which is weird.

This page is under a dynamic route [lang] (dynamic routing) like this : /[locale]/mypage. This page is not dynamic route juste a regular async page.

Anyone that also had this problem / could help with ?

I really need this page to process SearchParams before rendering (security purposes) . I use next 15.3.5
Answered by alfonsüs ardani
I mean you can also try doing it like this


/[locale]/[motherPageSlug]/page.tsx
/[locale]/[motherPageSlug]/[slug]/page.tsx
/[locale]/[motherPageSlug]/[slug]/draftMode/page.tsx
/[locale]/order/cancel/page.tsx
/[locale]/order/processing/page.tsx
/[locale]/order/success/page.tsx

but do NOT put await draftMode in /[locale]/layout.tsx.

use generateStaticParams in:
- /[locale]/layout.tsx
- /[locale]/[motherPageSlug]/layout.tsx
- /[locale]/[motherPageSlug]/[slug]/layout.tsx

and do not use force-static and force-dynamic
View full answer

123 Replies

Russian ToyOP
Hi, thank you for your help,
Here is the next build :
Route (app) Size First Load JS ┌ ○ /_not-found 977 B 102 kB ├ ○ /[locale] 409 B 150 kB ├ ○ /[locale]/[motherPageSlug] 518 B 150 kB ├ ○ /[locale]/[motherPageSlug]/[slug] 518 B 150 kB ├ ƒ /[locale]/order/cancel 329 B 115 kB ├ ƒ /[locale]/order/processing 328 B 115 kB ├ ƒ /[locale]/order/success 331 B 115 kB ├ ƒ /[locale]/sitemap.xml 147 B 101 kB ├ ƒ /api/exit-preview 147 B 101 kB ├ ƒ /api/orders/checkout 237 B 107 kB ├ ƒ /api/orders/payment/verify 237 B 107 kB ├ ƒ /api/preview 237 B 107 kB ├ ƒ /api/revalidate 237 B 107 kB ├ ○ /robots.txt 147 B 101 kB └ ○ /sitemap.xml 147 B 101 kB + First Load JS shared by all 101 kB ├ chunks/4bd1b696-8dd6532907bf1f42.js 53.2 kB ├ chunks/684-78be455ebeb65286.js 46.1 kB └ other shared chunks (total) 1.98 kB ƒ Middleware 49.1 kB ○ (Static) prerendered as static content ƒ (Dynamic) server-rendered on demand
@Russian Toy Click to see attachment
remove this part:

    console.log(` searchParams : ${JSON.stringify({ searchParams })}`);
and try again
you printed
the promise
not the searchParams object....
Russian ToyOP
console log from metadata does not change anything to the problem here, the logs from page show empty object :
thats weird :sweat:
Russian ToyOP
yes 😿
try console.log("SearchParams: ${ await searchParams }")
Russian ToyOP
still empty object
try moving this const translate = await getTranslations("Order"); below all the console.logs
Russian ToyOP
ok i will try
what the helly
time to troubleshoot and comment half of the code to see if its still empty
Russian ToyOP
i noticed i have a log saying this. :
pending revalidates promise finished for: { pathname: '/fr/order/success', query: undefined, search: '?sess=1234&ord=abcd', hash: ' href: '/fr/order/success?sess=abcd&ord=1234', slashes: undefined
in the order, i have the log from the page (empty), the log from generateMetadata (full), and this one above
this is messy 😅
where is this? pending revalidates promise finished for i didnt find it in the code
Russian ToyOP
it is logged in the terminal
from nextjs
:confused_nick_young: ive never gotten that consolelog from next before
Russian ToyOP
(i just upgraded to 15.5, to try if it was a bug that's been fixed, maybe that's why)
Russian ToyOP
i just find why: the rootlayout was using force-static, i removed it it works. But i need it for ISR purpose (& cost lol). My app is perfectly working other than that
best to determine static/dynamic directly in the page.tsx if you are not using ppr
and keep layout UI only... with little to no data fetches.. and no auth check
Russian ToyOP
i have a dynamic layout (using ISR in my layout) & i use draftmode here
i need it for Header / footer data
@Russian Toy i need it for Header / footer data
You can do that with parallel routes. Leaving the layout without any data fetching.

U cant just "ISR" a layout while leaving child pages SSR'd.
The decision to ISR ir SSG or SSR is computed to the whole route as a whole. Not individual segments.

If your layout is ISR but then its child is SSR, it will be confusing to maintain since its not clear if it will be ISR or SSR.

For example your /locale/order/cancel is SSR (denoted by the f symbol in your build log), including the root layout, layout of locale, layout of order and layout of cancel.
Russian ToyOP
in my case, i don't see how i can add parallel routes here ? You mean to remove all providers & dynamic in parallel routes ?
export default async function RootLayout({ children, params, }: { children: React.ReactNode; params: Promise<{ locale?: string }>; }) { const { isEnabled = false } = await draftMode(); const { locale } = await params; if (!locale || !isLocaleType(locale)) { notFound(); } return ( <html lang={locale} className={lato_font.variable}> <body> <NextIntlClientProvider> <ClientProviders> <LayoutWithDynamicZone isDraft={isEnabled} locale={locale}> {children} </LayoutWithDynamicZone> </ClientProviders> </NextIntlClientProvider> </body> </html> ); }
The website literally doesnt know anything about param at build time so how can it know if its not found or not? :ThinkEyes:
Forcing it to be static would just makes it weird and hard to debug
Draftmode uses headers so it will also convert static pages to dynamic SSR.

Do you really need draft mode at the root layout?
Russian ToyOP
I use next-intl for this purpose 🙂
@alfonsüs ardani The website literally doesnt know anything about param at build time so how can it know if its not found or not? <:ThinkEyes:711813052433039370>
Russian ToyOP
i had generateStaticParams in layout, but revalidatePath for child ISR was not working
I havent used next-intl so i cant help with that sorry.

But it is well known fact that next cant access param in build time since... there is no user...
@Russian Toy i had generateStaticParams in layout, but revalidatePath for child ISR was not working
Its because your child is all SSR. RevalidatePath doesnt affect caching of an SSR page. But they DO affect cached data inside an SSR page.
Cached data = data returned by fetch() and unstable_cach()
They dont revalidate SSR page since the page will always be freshly made per request
Russian ToyOP
i did use 'force-static' in my dynamic route and use revalidateTag("my-fetch-tag-on-dynamic-route")
but maybe i don't really understand how it works here
Dont use force static in dynamic route :/

It clearly breaks all your code here.

If you want dynamic page to be static you need generateStaticParam
Russian ToyOP
it was the only way i found where i can make it work for static build page and get draftmode working and get updated content with revalidateTag :/
Russian ToyOP
sorry, bad english, i wanted to say i wanted to implement ISR so i have updated content without rebuilding all website
then you put ISR here:

├ ○ /[locale]/[motherPageSlug]/page.tsx
├ ○ /[locale]/[motherPageSlug]/[slug]/page.tsx

not at /[locale]/layout.tsx
use generateStaticParam to generate dynamic routes so that it can be used statically. dont use force-static
Russian ToyOP
but how do i do if i have content that need to be revalidated on layout => header/footer
then dont put header/footer on /app/[locale]/layout.tsx

put it on parallel routes like /app/[locale]/@header/[motherPageSlug]/page.tsx
and
/app/[locale]/@footer/[motherPageSlug]/page.tsx

or just put both header/footer directly in
/[locale]/[motherPageSlug]/page.tsx
or
/[locale]/[motherPageSlug]/layout.tsx

but NOT
/app/[locale]/layout.tsx
Russian ToyOP
and what is the less expensive parallel routes or duplicating header/footer in layout where i need it ?
oh
i understand
you can still use layout.tsx in parallel routes
like this

/app/[locale]/@header/[motherPageSlug]/layout.tsx
/app/[locale]/@header/[motherPageSlug]/page.tsx
parallel routes is slightly less expensive in terms of size for each request
but its negligible if you dont have that much client component
why not put header/footer here?

/[locale]/[motherPageSlug]/layout.tsx
@alfonsüs ardani why not put header/footer here? `/[locale]/[motherPageSlug]/layout.tsx`
Russian ToyOP
i still need it for order routes (cancel, succes, etc)
but they are different header/footer no?
Russian ToyOP
no they are common, it is just the same data coming from headless cms
try separating the routes via route groups then just to be clear about which one is ISR and which one is SSR

like

/(static)/[locale]/[motherPageSlug]/[slug]/page.tsx
/(static)/[locale]/[motherPageSlug]/[slug]/page.tsx
/(dynamic)/[locale]/order/cancel/page.tsx
/(dynamic)/[locale]/order/processing/page.tsx
/(dynamic)/[locale]/order/success/page.tsx
that way you can put your ISR + revalidatePath on the /(static) folder
then also have the SSR of orders and consume params consume cookies headers and even draftmode in /(dynamic)
Russian ToyOP
you mean :
/[locale]/(static)/[motherPageSlug]/[slug]/page.tsx
/[locale]/(dynamic)/order/...
@Russian Toy you mean : /[locale]/(static)/[motherPageSlug]/[slug]/page.tsx /[locale]/(dynamic)/order/...
no, /(static)/[locale]/layout.tsx and /(dynamic)/[locale]/layout.tsx

you can't mix static logic and dynamic logic together :(((
hard to maintain
Russian ToyOP
it will work even if they a common dynamic route (locale) between (dynamic) & (static) ?
yes because you have a page under it.

lets say you access acme.com/en/order/cancel

it will automatically use /(dynamic)/[locale]/layout.tsx
and NOT /(static)/[locale]/layout.tsx
if you access acme.com/en/asdfasdfasdf/asdfasdf
then it will automatically use /(static)/[locale]/layout.tsx
Russian ToyOP
i dont know if this will work as it seems mandatory for next-intl : https://next-intl.dev/docs/routing/setup#layout
in /app ?
in rootlayout just return {children}...
nothing else
Russian ToyOP
i need to put the locale as lang in the html tag
<html lang={locale} ...>
// ...
</html>
yes put it in /(static)/[locales]/layout.tsx and /(dynamic)/[locales]/layout.tsx
Russian ToyOP
but they are conflicting path no ?
no?
read again
Russian ToyOP
ok i am trying it
and regarding pages for /[locale], i should duplicate like this : /(static)/[locales]/page.tsx and /(dynamic)/[locales]/page.tsx ? or only keep it in static ?
it static then put it in (static) if its dynamic then put it in (dynamic)
Russian ToyOP
ok ok
they dont have any effect to child path...
only layout.tsx have effect on child path
Russian ToyOP
ok thank you i try it
so like this ?
yes something like that.... it should work based on my experience
Russian ToyOP
ok i could build effectively this, order success page is working, but not the dynamic route pages
Russian ToyOP
i have a 500 error on it, sorry i mean i have errors on static/[locale]/[motherPageSlug] and static/[locale]/[motherPageSlug]/[slug]
well what is the error?
Russian ToyOP
if i use draftMode, i can still use draftMode, and generateStaticParams right ?
The error is : ⨯ [Error: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.] {
digest: 'DYNAMIC_SERVER_USAGE'
}
here is the new build FYI :
Route (app) Size First Load JS
┌ ƒ /_not-found 383 B 102 kB
├ ● /[locale] 395 B 151 kB
├ ├ /fr
├ └ /en
├ ● /[locale]/[motherPageSlug] 506 B 151 kB
├ ● /[locale]/[motherPageSlug]/[slug] 506 B 151 kB
├ ƒ /[locale]/order/cancel 316 B 116 kB
├ ƒ /[locale]/order/processing 317 B 116 kB
├ ƒ /[locale]/order/success 318 B 116 kB
├ ƒ /api/exit-preview 131 B 102 kB
├ ƒ /api/orders/checkout 227 B 107 kB
├ ƒ /api/orders/payment/verify 227 B 107 kB
├ ƒ /api/preview 227 B 107 kB
├ ƒ /api/revalidate 227 B 107 kB
├ ○ /robots.txt 131 B 102 kB
└ ○ /sitemap.xml 131 B 102 kB
+ First Load JS shared by all 102 kB
├ chunks/255-839588e0f3decf6f.js 45.7 kB
├ chunks/4bd1b696-c023c6e3521b1417.js 54.2 kB
└ other shared chunks (total) 2.01 kB


ƒ Middleware 50 kB

○ (Static) prerendered as static content
● (SSG) prerendered as static HTML (uses generateStaticParams)
ƒ (Dynamic) server-rendered on demand
no you can't use draftMode :(

I think you have to do something like this if you want draftMode

/(static)/[slug]/[childSlug]/page.tsx
/(dynamic)/[slug]/[childSlug]/draft-mode/page.tsx

and have a dedicated dynamic page for the draft-mode
Russian ToyOP
oh i see :/
I mean you can also try doing it like this


/[locale]/[motherPageSlug]/page.tsx
/[locale]/[motherPageSlug]/[slug]/page.tsx
/[locale]/[motherPageSlug]/[slug]/draftMode/page.tsx
/[locale]/order/cancel/page.tsx
/[locale]/order/processing/page.tsx
/[locale]/order/success/page.tsx

but do NOT put await draftMode in /[locale]/layout.tsx.

use generateStaticParams in:
- /[locale]/layout.tsx
- /[locale]/[motherPageSlug]/layout.tsx
- /[locale]/[motherPageSlug]/[slug]/layout.tsx

and do not use force-static and force-dynamic
Answer
if you put await draftMode in /[locale]/layout.tsx
it will change locale and all its children to be dynamically rendered.
and you can't fix it with force-static..
Russian ToyOP
i see !
i was thinking everything could work together
regarding draftmode and generateStaticParams and revalidatePath
yeah
it looks like that at first
but you are mixing static features with dynamic features xD and everything just went boom 🤯
Russian ToyOP
yes ^^
Russian ToyOP
ok i will try it and i will keep you updated, thank you so much, i feel that i understand much better how next js works 🙂
Russian ToyOP
you really help me so much ! thank you and if never you are in Bali, i would happy to offer the first drink !