Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 1 month ago by CosmicWanderer840

How can I resolve the useSearchParams Suspense boundary error in my Next.js production build?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

My Next.js production build fails with the error below, even though I wrapped my component code in Suspense tags:

BASH
⨯ useSearchParams() should be wrapped in a suspense boundary at page "/find/searchResults". Read more: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout at a (/home/yash/nextjs-hireme/hireme/.next/server/chunks/244.js:1:6747) at f (/home/yash/nextjs-hireme/hireme/.next/server/chunks/244.js:1:23270) at h (/home/yash/nextjs-hireme/hireme/.next/server/app/find/searchResults/page.js:1:3734) at nO (/home/yash/nextjs-hireme/hireme/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:20:45959) at nI (/home/yash/nextjs-hireme/hireme/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:20:47734) at nL (/home/yash/nextjs-hireme/hireme/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:20:65533) at nN (/home/yash/nextjs-hireme/hireme/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:20:63164) at n$ (/home/yash/nextjs-hireme/hireme/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:20:46311) at nI (/home/yash/nextjs-hireme/hireme/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:20:47780) at nI (/home/yash/nextjs-hireme/hireme/node_modules/next/dist/compiled/next-server/app-page.runtime.prod.js:20:62515) Error occurred prerendering page "/find/searchResults". Read more: https://nextjs.org/docs/messages/prerender-error Export encountered an error on /find/searchResults/page: /find/searchResults, exiting the build. ⨯ Static worker exited with code: 1 and signal: null

I have also reinstalled my dependencies, yet the issue persists. The page in question displays search results fetched by the search-services component. Below is my folder structure:

app/
├── components/
|      ├── search-services.jsx 
|
├── find/
│     ├── searchResults/
│     |         ├── page.jsx
│     ├── page.jsx

And here is my code:

JAVASCRIPT
"use client"; import React, { useEffect, useState, Suspense } from "react"; import { useSearchParams } from "next/navigation"; import searchServices from "../../components/search-services"; import Link from "next/link"; import ServiceDetails from "../../components/service-details"; import { auth, db } from "../../firebase/config"; import { doc, getDoc } from "firebase/firestore"; import calculateDistance from "../../components/calculate-distance"; const SearchResults = () => { const searchParams = useSearchParams(); const searchTerm = searchParams.get("query").trim() || ""; const [results, setResults] = useState([]); const [loading, setLoading] = useState(true); const [selectedService, setSelectedService] = useState(null); const [userLocation, setUserLocation] = useState(null); const handleServiceClick = (service) => setSelectedService(service); const handleCloseDetails = () => setSelectedService(null); // Fetch user location from Firestore useEffect(() => { const fetchUserLocation = async () => { auth.onAuthStateChanged(async (user) => { if (user) { const docRef = doc(db, "Users", user.uid); const docSnap = await getDoc(docRef); if (docSnap.exists()) { const userData = docSnap.data(); if (userData.location) { setUserLocation(userData.location); // Assume location is { latitude, longitude } } } } else { console.log("User is not logged in"); } }); }; fetchUserLocation(); }, []); // Fetch search results useEffect(() => { const fetchResults = async () => { if (!searchTerm) return; setLoading(true); try { const services = await searchServices(searchTerm); setResults(services); } catch (error) { console.error("Error fetching search results:", error); } setLoading(false); }; fetchResults(); }, [searchTerm]); return ( <Suspense> <div className="min-h-screen bg-gray-50 px-4 py-6"> <h1 className="text-2xl font-semibold mb-4">Search Results for "{searchTerm}"</h1> <Link href="/find" className="text-teal-600 mt-4 inline-block"> Back to Search </Link> {loading ? ( <p>Loading...</p> ) : results.length === 0 ? ( <p className="text-gray-500">No services found.</p> ) : ( <div className="grid grid-cols-1 gap-4"> {results.map((service) => { const distance = userLocation && service.location ? calculateDistance(userLocation, service.location) : null; return ( <div key={service.id} className="relative p-4 border rounded-lg cursor-pointer hover:bg-gray-50 transition-colors shadow-sm" onClick={(e) => { e.stopPropagation(); handleServiceClick(service); }}> <div className="flex justify-between items-center"> <h2 className="text-lg font-semibold text-gray-800">{service.title}</h2> </div> <p className="text-gray-600 mt-2 line-clamp-2">{service.description}</p> <div className="mt-2"> <div className="flex justify-between"> <div> <p className="text-sm text-gray-500">Provider: {service.providerName}</p> <p className="text-sm text-gray-500">Phone: {service.providerPhone}</p> <p className="text-sm text-gray-500">Email: {service.providerEmail}</p> </div> {distance !== null && ( <div className="text-right"> <span className="text-md font-bold text-blue-700"> {distance.toFixed(2)} km </span> </div> )} </div> </div> <div className="mt-4 flex justify-between items-center"> <p className="text-teal-600 font-bold mb-2"> {parseFloat(service.price.min).toFixed(2)} -{parseFloat(service.price.max).toFixed(2)} </p> <span className="text-sm text-gray-500">{new Date(service.timestamp).toLocaleDateString('en-GB')}</span> </div> </div> ); })} </div> )} {/* Display selected service details */} {selectedService && ( <ServiceDetails key={selectedService.userId} userId={selectedService.userId} service={selectedService} onClose={handleCloseDetails} /> )} </div> </Suspense> ); }; export default SearchResults;

I am running Next.js 15.1.6 and would appreciate any guidance on how to correctly wrap useSearchParams with a Suspense boundary so that the error is resolved.

1

Comments 0

Answers 3

Views 49

3 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

Below is an explanation of what’s happening and a recommended solution.


What’s Going On

When you call useSearchParams in a client component that may be statically rendered, Next.js needs the entire client tree that uses it to be wrapped in a Suspense boundary. In your code the hook is declared at the top-level of your SearchResults component and—even though you wrap your JSX inside a Suspense—the hook is called before the Suspense takes effect. As a result, during prerendering the call isn’t “caught” by any Suspense boundary and Next.js generates the error.

Recommended Fix

The easiest way to fix this is to move your SearchResults code that calls useSearchParams into its own client component and then wrap that component in a Suspense boundary from the parent (for example, in your page component). This ensures that the hook call only happens inside a part of the tree that is rendered later via Suspense.


Example Implementation

  1. Create a Client-Only Component

Move the logic that uses useSearchParams into its own file (for example, SearchResultsContent.jsx) with the "use client" directive. For instance:

JSX
// app/find/searchResults/SearchResultsContent.jsx "use client"; import React, { useEffect, useState } from "react"; import { useSearchParams } from "next/navigation"; import searchServices from "../../components/search-services"; import Link from "next/link"; import ServiceDetails from "../../components/service-details"; import { auth, db } from "../../firebase/config"; import { doc, getDoc } from "firebase/firestore"; import calculateDistance from "../../components/calculate-distance"; export default function SearchResultsContent() { const searchParams = useSearchParams(); const searchTerm = (searchParams.get("query") || "").trim(); const [results, setResults] = useState([]); const [loading, setLoading] = useState(true); const [selectedService, setSelectedService] = useState(null); const [userLocation, setUserLocation] = useState(null); const handleServiceClick = (service) => setSelectedService(service); const handleCloseDetails = () => setSelectedService(null); // Fetch user location from Firestore useEffect(() => { const fetchUserLocation = async () => { auth.onAuthStateChanged(async (user) => { if (user) { const docRef = doc(db, "Users", user.uid); const docSnap = await getDoc(docRef); if (docSnap.exists()) { const userData = docSnap.data(); if (userData.location) { setUserLocation(userData.location); // { latitude, longitude } } } } else { console.log("User is not logged in"); } }); }; fetchUserLocation(); }, []); // Fetch search results useEffect(() => { const fetchResults = async () => { if (!searchTerm) return; setLoading(true); try { const services = await searchServices(searchTerm); setResults(services); } catch (error) { console.error("Error fetching search results:", error); } setLoading(false); }; fetchResults(); }, [searchTerm]); return ( <div className="min-h-screen bg-gray-50 px-4 py-6"> <h1 className="text-2xl font-semibold mb-4">Search Results for "{searchTerm}"</h1> <Link href="/find" className="text-teal-600 mt-4 inline-block"> ← Back to Search </Link> {loading ? ( <p>Loading...</p> ) : results.length === 0 ? ( <p className="text-gray-500">No services found.</p> ) : ( <div className="grid grid-cols-1 gap-4"> {results.map((service) => { const distance = userLocation && service.location ? calculateDistance(userLocation, service.location) : null; return ( <div key={service.id} className="relative p-4 border rounded-lg cursor-pointer hover:bg-gray-50 transition-colors shadow-sm" onClick={(e) => { e.stopPropagation(); handleServiceClick(service); }} > <div className="flex justify-between items-center"> <h2 className="text-lg font-semibold text-gray-800">{service.title}</h2> </div> <p className="text-gray-600 mt-2 line-clamp-2">{service.description}</p> <div className="mt-2"> <div className="flex justify-between"> <div> <p className="text-sm text-gray-500">Provider: {service.providerName}</p> <p className="text-sm text-gray-500">Phone: {service.providerPhone}</p> <p className="text-sm text-gray-500">Email: {service.providerEmail}</p> </div> {distance !== null && ( <div className="text-right"> <span className="text-md font-bold text-blue-700"> {distance.toFixed(2)} km </span> </div> )} </div> </div> <div className="mt-4 flex justify-between items-center"> <p className="text-teal-600 font-bold mb-2"> {parseFloat(service.price.min).toFixed(2)} - ₹{parseFloat(service.price.max).toFixed(2)} </p> <span className="text-sm text-gray-500"> {new Date(service.timestamp).toLocaleDateString("en-GB")} </span> </div> </div> ); })} </div> )} {/* Display selected service details */} {selectedService && ( <ServiceDetails key={selectedService.userId} userId={selectedService.userId} service={selectedService} onClose={handleCloseDetails} /> )} </div> ); }
  1. Wrap the Client Component in a Suspense Boundary

Now, update your page file to wrap this component in a Suspense boundary. For example:

JSX
// app/find/searchResults/page.jsx import React, { Suspense } from "react"; import SearchResultsContent from "./SearchResultsContent"; export default function SearchResultsPage() { return ( <Suspense fallback={<div>Loading search results...</div>}> <SearchResultsContent /> </Suspense> ); }

Why This Works

By moving the code that calls useSearchParams into its own client component (SearchResultsContent) and then wrapping that component in Suspense from the page file, you ensure that the hook is not invoked before the Suspense boundary takes effect. This satisfies Next.js’s requirement and resolves the build error.

Give this approach a try and your production build error should be resolved.


If you have further questions about Next.js or client component rendering, please let me know!

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Answer by LunarScout554 1 month ago

0

I would not recommend rendering the whole page client side, but if you want to keep it this way you can do this:

PLAINTEXT
app/ ├── find/ │ ├── searchResults/ │ │ ├── layout.tsx │ | ├── page.jsx

in the layout.tsx you can use suspense component and wrap children prop inside:

TYPESCRIPT
import { Suspense } from "react"; export default function SRLayout({ children }: { children: React.ReactNode }) { return ( <Suspense> {children} </Suspense> ) }

This approach is not ideal since it beats the whole point of partial prerendering and server side rendering.

No comments yet.

Answer by SupernovaObserver123 1 month ago

0

Solved it by by calling the SearchResults component inside the Page component and wrapping it in a Suspense block.

JAVASCRIPT
import { Suspense } from "react"; const searchResults = () => { // code } const Page = () => { return ( <Suspense> <searchResults /> </Suspense> ) } export default Page

No comments yet.

Discussion

No comments yet.