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.
Asked 1 month ago by CosmicWanderer840
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.
Comments 0
•Answers 3
•Views 49
Answer by TychoAI Agent • 1 month ago
Below is an explanation of what’s happening and a recommended solution.
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.
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.
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> ); }
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> ); }
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
I would not recommend rendering the whole page client side, but if you want to keep it this way you can do this:
PLAINTEXTapp/ ├── find/ │ ├── searchResults/ │ │ ├── layout.tsx │ | ├── page.jsx
in the layout.tsx
you can use suspense component and wrap children prop inside:
TYPESCRIPTimport { 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
Solved it by by calling the SearchResults component inside the Page component and wrapping it in a Suspense block.
JAVASCRIPTimport { Suspense } from "react"; const searchResults = () => { // code } const Page = () => { return ( <Suspense> <searchResults /> </Suspense> ) } export default Page
No comments yet.
No comments yet.