Frameworks / useSearchParams + Suspense silently hides static content from AI crawlers/SEO

Hi Vercel team,

I wanted to share a real-world case that might be useful feedback on the useSearchParams() + Suspense interaction in the App Router (related to GitHub issue #74494).

I’m the founder/developer of Furway (https://furway.app), a community-driven platform for stray animal welfare. On our marketing site’s /download page (Furway), we had useSearchParams() (used for UTM campaign tracking) inside a "use client" component, with the entire component — static headline, paragraphs, AND the form — wrapped in one <Suspense> boundary:

```

```

Because of how the CSR bailout works, this meant the whole page, including content that had nothing to do with search params, was client-side rendered. We only discovered this when testing whether AI tools like ChatGPT and Claude could read our page’s content — they came back with an essentially blank result. For a small platform relying on organic discovery and AI-search visibility, that’s a meaningful, easy-to-miss cost.

The fix was straightforward once identified: move static content to a plain Server Component, isolate the useSearchParams()-dependent logic into a small child Client Component, and scope the <Suspense> boundary to just that child. But nothing in the current docs or dev-time warnings made the “everything inside this boundary gets CSR’d, including unrelated static content” behavior obvious — we only found the explanation by digging into GitHub discussions.

A dev-mode warning (or lint rule) flagging “this Suspense boundary wraps static content that will be excluded from prerendering” would likely save other small teams from the same silent SEO/AI-visibility regression.

Happy to share more details about our case if useful.

Thanks for all the work on Next.js — it’s been a great fit for building Furway.

Best,
Christina
Founder, Furway (furway.app)

Update: the fix I described above (splitting into Server/Client components with Suspense scoped to just the form) turned out to be incomplete. The page still appeared empty to fetch-based crawlers, because any use of the searchParams prop (even just reading it) forces the whole route to be dynamically rendered — which seems to be what actually broke crawler visibility, not the Suspense boundary itself. The real fix: remove searchParams entirely from the Server Component, and read the URL parameter purely client-side via window.location.search inside the Client Component’s useEffect. This lets the route be fully statically prerendered (confirmed via `next build` output showing ○ Static instead of ƒ Dynamic), which resolved the crawler visibility issue completely.