Common UseSearchParams Mistakes in NextJS
TW
Tech Wizard
Author
Jul 25, 2025
4 min read

Common UseSearchParams Mistakes in NextJS

1

URLSearchparams provide the most safe way of managing state that ensures that your state is persistent, but in NextJS this means that your component must be dynamic since NextJS cannot know what these search params will be in advance. If you've ever tried using searchParams in a Next.js app and found yourself staring at hydration errors, missing query values, or strange crashes — you're definitely not alone. I’ve run into all these headaches and more. But once I started understanding how this hook works (and when it doesn’t), it completely changed how I handle state in the URL.

Why SearchParams are Great for State Management

Before diving into the mistakes, let’s quickly talk about why URLSearchParams (and by extension, useSearchParams()) is such a solid tool for managing state:

  • It persists across reloads: Since it's tied to the URL, your state survives full page refreshes without any extra setup.
  • It creates shareable URLs: Users can copy and share the exact state of your page — filters, tabs, pagination, etc.
  • It’s built-in: No need to bring in Redux, Zustand, or even useState for simple UI state.
  • Works great with shallow routing: Update the URL without reloading the entire page. Clean and efficient.

Now, let’s walk through the common mistakes you might encounter — and how to fix each one.

Mistake 1: Not Wrapping useSearchParams() in a <Suspense> Boundary

One of the first cryptic errors I ran into was this:

Error: useSearchParams() should be wrapped in a suspense boundary

This confused me at first, but here's the deal: useSearchParams() is asynchronous behind the scenes. That means Next.js expects it to be used inside a <Suspense> boundary so it can resolve the data correctly during rendering.

✅ Fix: Wrap the component that uses useSearchParams() in <Suspense>:


import { Suspense } from 'react';
import { useSearchParams } from 'next/navigation';

function SearchComponent() {
  const searchParams = useSearchParams();
  const query = searchParams.get('q') || 'none';
  return <div>Query: {query}</div>;
}

export default function Page() {
  return (
    <Suspense fallback=<div>Loading...</div>>
      <SearchComponent />
    </Suspense>
  );
}

Mistake 2: Using useSearchParams() in a Server Component

This one's a classic — you drop useSearchParams() into a component and forget it's only available on the client. Boom! Server rendering error.


// ❌ This will break!
import { useSearchParams } from 'next/navigation';

export default function ServerComponent() {
  const searchParams = useSearchParams(); // ❌ Invalid here
  return <div>{searchParams.get('q')}</div>;
}

✅ Fix: Add 'use client' at the top of your file to mark it as a client component:


'use client';

import { useSearchParams } from 'next/navigation';

export default function ClientComponent() {
  const searchParams = useSearchParams();
  return <div>{searchParams.get('q')}</div>;
}

That 'use client' directive isn’t optional — it tells Next.js this component should only render in the browser.

Mistake 3: Assuming the Search Param Always Exists

This one tripped me up when I called .toLowerCase() on a param that was actually null.


const query = searchParams.get("query").toLowerCase(); // 💥 crash if null

✅ Fix: Always provide a fallback value, or check for null explicitly:


const rawQuery = searchParams.get("query");
const query = typeof rawQuery === 'string' ? rawQuery.toLowerCase() : 'default';

Don’t trust the URL to always include what you expect — your app will thank you.

Mistake 4: Trying to Modify searchParams Directly

The object returned from useSearchParams() is readonly. You can’t just call set() on it.


searchParams.set("filter", "active"); // ❌ this throws an error

✅ Fix: Create a new instance using URLSearchParams and pass it to router.push():


const params = new URLSearchParams(searchParams.toString());
params.set("filter", "active");
router.push(`?${params.toString()}`);

Think of searchParams like a snapshot — you’ll need to clone it if you want to make changes.

Mistake 5: Forgetting to Encode/Decode Parameter Values

If your query values include spaces or special characters, they’ll break your URLs unless you handle encoding properly.


params.set("name", "John Doe & Sons"); // ❌ results in broken query string

✅ Fix: Encode before setting, decode when reading:


params.set("name", encodeURIComponent("John Doe & Sons"));
...
const name = decodeURIComponent(searchParams.get("name") || "");

While URLSearchParams will do some encoding for you automatically, it’s a good idea to be explicit, especially when dealing with user input.

✅ Full Working Example


'use client';

import React, { Suspense } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';

function SearchComponent() {
  const searchParams = useSearchParams();
  const router = useRouter();

  const query = searchParams.get("query") || "default";

  const setParams = () => {
    const params = new URLSearchParams(searchParams.toString());
    params.set("query", "Hello World");
    router.push(`?${params.toString()}`);
  };

  return (
    <div>
      <h1>Search Component</h1>
      <p>Query: {query}</p>
      <button onClick={setParams}>Set Query</button>
    </div>
  );
}

export default function Page() {
  return (
    <Suspense fallback=<div>Loading...</div>>
      <SearchComponent />
    </Suspense>
  );
}

Conclusion

I’ve definitely made all of these mistakes — and probably a few more. But once you understand how useSearchParams() works and how it fits into the Next.js rendering model, it becomes an incredibly useful tool.

Just remember:

  • ✅ Wrap components with <Suspense>
  • ✅ Use 'use client' when needed
  • ✅ Handle null safely
  • ✅ Don’t mutate searchParams directly
  • ✅ Encode your values when needed
1
0