勇哥

勇哥开发之路

I build things on web.

Next.js Pitfalls of useSearchParams()

❗Problem#

Recently, Yongge was working on an AI dialogue application and used the useSearchParams method to obtain parameters from the URL. Everything worked fine in development mode, but an error occurred during the build:

 useSearchParams() should be wrapped in a suspense boundary at page "/".

❓Reason#

The official explanation is:
Reading search parameters through useSearchParams() without a Suspense boundary will cause the entire page to enter client-side rendering. This may result in the page remaining blank until the client-side JavaScript loads.

Since Yongge is using static rendering, the useSearchParams method returns a read-only interface of URLSearchParams. The URLSearchParams relies on the browser, meaning it must wait for the window object to load before it can be used.

✔Solution#

The official recommended solution is also very simple: ensure that the call to useSearchParams() is wrapped in Suspense.

Before the modification, Yongge's page.tsx file looked like this:

'use client';

...
import { useRouter, useSearchParams } from 'next/navigation'
import { SidebarProvider, SidebarInset } from "@/components/ui/sidebar"
...

// Main page component
export default function Page() {
  const router = useRouter()
  const urlParams = useSearchParams()
  // ... other states and logic
  
  return (
    <SidebarProvider>
      <AppSidebar />
      <SidebarInset>
        ...
      </SidebarInset>
    </SidebarProvider>
  );
}

After the modification:

'use client';

import { Suspense } from "react"
import { useRouter, useSearchParams } from 'next/navigation'
import { SidebarProvider, SidebarInset } from "@/components/ui/sidebar"

// Create a wrapper component to handle all client-side logic
function ChatPageContent() {
  // ... original states and logic
    const router = useRouter()
    const urlParams = useSearchParams()
    // ... other states and logic
    
    return (
      <SidebarProvider>
        <AppSidebar />
        <SidebarInset>
          ...
        </SidebarInset>
      </SidebarProvider>
    );
  }
}

// Main page component
export default function Page() {
  // Add <Suspense /> outside the wrapper component
  return (
    <Suspense fallback={<div>Loading chat...</div>}>
      <ChatPageContent />
    </Suspense>
  );
}

Wrap the client component that uses useSearchParams in <Suspense/>. In this case, part of the route will be statically rendered, while the dynamic part using useSearchParams will be rendered on the client after the window object becomes available.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.