Adding a search box to a statically generated blog index looks trivial. It is not, and the failure mode is invisible in a browser.
The problem
Filter state naturally lives in the URL, so you reach for useSearchParams.
That hook forces the route to render dynamically unless it sits inside a
Suspense boundary. So you wrap it — and the build passes.
The trap is what the boundary renders while it waits.
Why the fallback is the whole decision
During static generation, the Suspense fallback is what gets written into the HTML file. That file is what a crawler downloads.
If your fallback is a spinner, your blog index ships to Google as a loading state. Every post becomes invisible. And you will never notice by looking, because hydration replaces the fallback in milliseconds.
The fix
Render the complete, unfiltered list as the fallback:
<Suspense fallback={<PostCards posts={allPosts} />}>
<FilterablePostList posts={allPosts} />
</Suspense>The cost is that opening a pre-filtered URL shows everything for one frame before narrowing. That is a fair trade against being unindexable.