CCM
/Skills
SkillsMCPMarketplacesDigestToolsAdvertise

This week in Claude

Every Monday: Claude Code, Agent SDK, MCP, and the Anthropic platform moves worth your time.

Skills by Category
Frontend DevelopmentBackend & APIsTesting & QASecurityDevOps & CI/CDGit & Pull RequestsDocumentationCode Review & QualityAI & Agent BuildingSkill Development
MCP Servers by Category
Sales & MarketingWeb & Browser AutomationDatabasesAI & LLM ToolsCloud & InfrastructureCommunication & MessagingDeveloper ToolsDesign & CreativeDocuments & KnowledgeSearch & Web Crawling
Marketplaces by Category
AI Agents & OrchestrationLLM IntegrationDevelopment ToolsFrontend & UIBackend & APIsDatabasesTesting & Code QualityDevOps & CloudSecurity & ComplianceGit & Version Control

Claude Code Marketplaces

Discover Claude Code plugins, extensions, and tools. Automatically updated directory of Anthropic Claude AI marketplaces with development tools, productivity plugins, and integrations.

Resources

  • Browse Skills
  • Browse MCP Servers
  • Browse Marketplaces
  • Skill index
  • MCP index
  • Marketplace index
  • Plugins Reference

Community

  • About
  • Tools
  • Feedback
  • Privacy Policy
  • Advertise

Built for the Claude Code community with Claude Code by mertbuilds.com

Independent project, not affiliated with Anthropic
wsimmonds avatar

Nextjs Use Search Params Suspense

wsimmonds/claude-nextjs-skills
287 installs109 stars
Summary

If you've hit the Next.js error about useSearchParams needing a Suspense boundary, this walks you through the required pattern. Every component using useSearchParams needs both the 'use client' directive and a Suspense wrapper around it. The skill covers the two main approaches (separate component vs inline), shows how to read and update URL params for search interfaces, filters, and pagination, and includes practical examples with TypeScript types. It also demonstrates working with URLSearchParams methods and updating the URL without page refreshes using useRouter. Useful reference when building any feature that reads query strings client-side in Next.js App Router.

Install to Claude Code

npx -y skills add wsimmonds/claude-nextjs-skills --skill nextjs-use-search-params-suspense --agent claude-code

Installs into .claude/skills of the current project.

CodeRabbit
CodeRabbit
AI writes the code. CodeRabbit catches the slop.
Try For Free →
MCP-ready Email SendingMCP-ready Email Sending
MCP-ready Email Sending
Plug Mailtrap into your AI workflow and let it handle the email.
Connect Mailtrap MCP →
Make your agent a DeFi expert
Make your agent a DeFi expert
Agent, run crypto. Access onchain data & trade routes via 1inch.
Install now →
Capacitor - Shared memory for your team’s coding agents.
Capacitor - Shared memory for your team’s coding agents.
Make coding agent sessions - Searchable, Shareable, Vendor-neutral & Scored.
Try For Free →
CodeScene MCP ServerCodeScene MCP Server
CodeScene MCP Server
Your agent targets a perfect 10 Code Health score. Deterministic. Every commit.
Try For Free →
Give your AI the whole web as clean markdownGive your AI the whole web as clean markdown
Give your AI the whole web as clean markdown
Integrate web data into your AI product. One API to scrape website & brand data.
Get API Key Now →
belt - the only tool your agent needs
belt - the only tool your agent needs
belt cli automatically finds the best tools and skills for your agent. image, video, music, tts...
one prompt install →
inference shell
inference shell
create and run specialised agents in minutes
build now →
CodeRabbit
CodeRabbit
AI writes the code. CodeRabbit catches the slop.
Try For Free →
MCP-ready Email SendingMCP-ready Email Sending
MCP-ready Email Sending
Plug Mailtrap into your AI workflow and let it handle the email.
Connect Mailtrap MCP →
Make your agent a DeFi expert
Make your agent a DeFi expert
Agent, run crypto. Access onchain data & trade routes via 1inch.
Install now →
Capacitor - Shared memory for your team’s coding agents.
Capacitor - Shared memory for your team’s coding agents.
Make coding agent sessions - Searchable, Shareable, Vendor-neutral & Scored.
Try For Free →
CodeScene MCP ServerCodeScene MCP Server
CodeScene MCP Server
Your agent targets a perfect 10 Code Health score. Deterministic. Every commit.
Try For Free →
Give your AI the whole web as clean markdownGive your AI the whole web as clean markdown
Give your AI the whole web as clean markdown
Integrate web data into your AI product. One API to scrape website & brand data.
Get API Key Now →
belt - the only tool your agent needs
belt - the only tool your agent needs
belt cli automatically finds the best tools and skills for your agent. image, video, music, tts...
one prompt install →
inference shell
inference shell
create and run specialised agents in minutes
build now →
Files
SKILL.mdView on GitHub

Next.js: useSearchParams with Suspense Pattern

Pattern Overview

The useSearchParams hook requires TWO things:

  1. Component must have 'use client' directive
  2. Component must be wrapped in a <Suspense> boundary

This is a Next.js requirement, not optional!

Why This Pattern?

useSearchParams reads URL query parameters:

  • /search?q=shoes → searchParams.get('q') returns "shoes"
  • /products?category=electronics&sort=price → Read multiple params

Why Suspense? Next.js uses React 18's Suspense to handle the async nature of reading URL params during server-side rendering and hydration.

The Pattern

Single-File Pattern (Recommended)

// app/page.tsx
import { Suspense } from 'react';
import SearchComponent from './SearchComponent';

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

// app/SearchComponent.tsx
'use client';

import { useSearchParams } from 'next/navigation';

export default function SearchComponent() {
  const searchParams = useSearchParams();
  const query = searchParams.get('q') || '';

  return (
    <div>
      <h1>Search Results for: {query}</h1>
    </div>
  );
}

Inline Pattern (Single File)

Sometimes you want everything in one file:

// app/page.tsx
'use client';

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

function SearchContent() {
  const searchParams = useSearchParams();
  const query = searchParams.get('q') || '';

  return (
    <div>
      <h1>Search: {query}</h1>
      <p>Results for "{query}"</p>
    </div>
  );
}

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

TypeScript: NEVER Use any Type

// ❌ WRONG
function Component({ params }: any) { ... }

// ✅ CORRECT
// useSearchParams returns ReadonlyURLSearchParams
function Component() {
  const searchParams = useSearchParams();
  const value: string | null = searchParams.get('key');
}

Real-World Examples

Example 1: Search Interface

// app/search/page.tsx
'use client';

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

function SearchResults() {
  const searchParams = useSearchParams();
  const query = searchParams.get('q') || '';
  const category = searchParams.get('category') || 'all';

  return (
    <div>
      <h1>Search: {query}</h1>
      <p>Category: {category}</p>

      {/* Display search results */}
      <div className="results">
        {/* ... */}
      </div>
    </div>
  );
}

export default function SearchPage() {
  return (
    <div>
      <Suspense fallback={<div>Loading results...</div>}>
        <SearchResults />
      </Suspense>
    </div>
  );
}

Example 2: Product Filters

// app/products/page.tsx
'use client';

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

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

  const category = searchParams.get('category') || 'all';
  const sort = searchParams.get('sort') || 'name';
  const minPrice = searchParams.get('minPrice') || '0';

  const updateFilter = (key: string, value: string) => {
    const params = new URLSearchParams(searchParams.toString());
    params.set(key, value);
    router.push(`?${params.toString()}`);
  };

  return (
    <div>
      <div className="filters">
        <select
          value={category}
          onChange={(e) => updateFilter('category', e.target.value)}
        >
          <option value="all">All Categories</option>
          <option value="electronics">Electronics</option>
          <option value="clothing">Clothing</option>
        </select>

        <select
          value={sort}
          onChange={(e) => updateFilter('sort', e.target.value)}
        >
          <option value="name">Name</option>
          <option value="price">Price</option>
          <option value="rating">Rating</option>
        </select>
      </div>

      <div className="products">
        {/* Product grid filtered by params */}
      </div>
    </div>
  );
}

export default function ProductsPage() {
  return (
    <Suspense fallback={<div>Loading products...</div>}>
      <ProductList />
    </Suspense>
  );
}

Example 3: Pagination

// app/blog/page.tsx
'use client';

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

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

  const page = parseInt(searchParams.get('page') || '1', 10);
  const perPage = 10;

  const goToPage = (newPage: number) => {
    const params = new URLSearchParams(searchParams.toString());
    params.set('page', newPage.toString());
    router.push(`?${params.toString()}`);
  };

  return (
    <div>
      <h1>Blog Posts - Page {page}</h1>

      <div className="posts">
        {/* Blog posts for current page */}
      </div>

      <div className="pagination">
        <button
          disabled={page === 1}
          onClick={() => goToPage(page - 1)}
        >
          Previous
        </button>
        <span>Page {page}</span>
        <button onClick={() => goToPage(page + 1)}>
          Next
        </button>
      </div>
    </div>
  );
}

export default function BlogPage() {
  return (
    <Suspense fallback={<div>Loading posts...</div>}>
      <BlogPosts />
    </Suspense>
  );
}

Working with URLSearchParams

'use client';

import { useSearchParams } from 'next/navigation';

function Component() {
  const searchParams = useSearchParams();

  // Get single value
  const query = searchParams.get('q');           // string | null
  const category = searchParams.get('category'); // string | null

  // Get all values for a key (for multi-select)
  const tags = searchParams.getAll('tag');       // string[]

  // Check if key exists
  const hasSort = searchParams.has('sort');      // boolean

  // Iterate over all params
  searchParams.forEach((value, key) => {
    console.log(`${key}: ${value}`);
  });

  // Convert to regular object
  const paramsObject = Object.fromEntries(searchParams.entries());

  return <div>{/* ... */}</div>;
}

Updating URL Parameters

'use client';

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

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

  const updateParams = (updates: Record<string, string>) => {
    // Create new URLSearchParams from current params
    const params = new URLSearchParams(searchParams.toString());

    // Apply updates
    Object.entries(updates).forEach(([key, value]) => {
      if (value) {
        params.set(key, value);
      } else {
        params.delete(key);  // Remove if value is empty
      }
    });

    // Navigate with new params
    router.push(`?${params.toString()}`);
  };

  return (
    <button onClick={() => updateParams({ sort: 'price', order: 'asc' })}>
      Sort by Price
    </button>
  );
}

Common Patterns

Pattern: Search with Debounce

'use client';

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

function SearchInput() {
  const searchParams = useSearchParams();
  const router = useRouter();
  const [query, setQuery] = useState(searchParams.get('q') || '');

  useEffect(() => {
    const timer = setTimeout(() => {
      const params = new URLSearchParams(searchParams.toString());
      if (query) {
        params.set('q', query);
      } else {
        params.delete('q');
      }
      router.push(`?${params.toString()}`);
    }, 300); // Debounce 300ms

    return () => clearTimeout(timer);
  }, [query, searchParams, router]);

  return (
    <input
      type="search"
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="Search..."
    />
  );
}

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

Pattern: Multiple Filters

'use client';

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

interface Filters {
  category?: string;
  priceMin?: string;
  priceMax?: string;
  inStock?: string;
}

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

  const currentFilters: Filters = {
    category: searchParams.get('category') || undefined,
    priceMin: searchParams.get('priceMin') || undefined,
    priceMax: searchParams.get('priceMax') || undefined,
    inStock: searchParams.get('inStock') || undefined,
  };

  const updateFilters = (newFilters: Partial<Filters>) => {
    const params = new URLSearchParams(searchParams.toString());

    Object.entries({ ...currentFilters, ...newFilters }).forEach(
      ([key, value]) => {
        if (value) {
          params.set(key, value);
        } else {
          params.delete(key);
        }
      }
    );

    router.push(`?${params.toString()}`);
  };

  const clearFilters = () => {
    router.push(window.location.pathname); // Remove all params
  };

  return (
    <div className="filters">
      <select
        value={currentFilters.category || ''}
        onChange={(e) => updateFilters({ category: e.target.value })}
      >
        <option value="">All Categories</option>
        <option value="electronics">Electronics</option>
      </select>

      <input
        type="number"
        placeholder="Min Price"
        value={currentFilters.priceMin || ''}
        onChange={(e) => updateFilters({ priceMin: e.target.value })}
      />

      <button onClick={clearFilters}>Clear Filters</button>
    </div>
  );
}

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

Common Mistakes

❌ Mistake 1: Missing 'use client'

// ❌ WRONG - Missing 'use client'
import { useSearchParams } from 'next/navigation';

export default function Page() {
  const searchParams = useSearchParams(); // ERROR!
  return <div>{searchParams.get('q')}</div>;
}
// ✅ CORRECT
'use client';  // Added!

import { useSearchParams } from 'next/navigation';

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

❌ Mistake 2: Missing Suspense Wrapper

// ❌ WRONG - Missing Suspense
'use client';

import { useSearchParams } from 'next/navigation';

export default function Page() {
  const searchParams = useSearchParams(); // Will cause issues!
  return <div>{searchParams.get('q')}</div>;
}
// ✅ CORRECT
'use client';

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

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

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

❌ Mistake 3: Using in Server Component

// ❌ WRONG - Trying to use in server component
import { useSearchParams } from 'next/navigation';

export default async function Page() {  // async = server component
  const searchParams = useSearchParams(); // ERROR! Hooks don't work in server components
  return <div>...</div>;
}
// ✅ CORRECT - Use searchParams prop in server components
export default async function Page({
  searchParams,
}: {
  searchParams: Promise<{ q?: string }>;
}) {
  const { q } = await searchParams;
  return <div>Query: {q}</div>;
}

Server vs Client searchParams

FeatureServer ComponentClient Component
Access methodsearchParams propuseSearchParams() hook
Requires 'use client'❌ No✅ Yes
Requires Suspense❌ No✅ Yes
Can be async✅ Yes❌ No
Can update params❌ No (use Link/redirect)✅ Yes (use router.push)
Best forInitial load, SEODynamic filters, real-time updates

Quick Checklist

When using useSearchParams:

  • Add 'use client' directive at top of file
  • Import Suspense from 'react'
  • Import useSearchParams from 'next/navigation'
  • Wrap component using useSearchParams in <Suspense>
  • Provide a fallback to Suspense
  • Call useSearchParams() inside wrapped component
  • Use .get(), .has(), or .getAll() to read params

Summary

useSearchParams with Suspense:

  • ✅ Requires 'use client' directive
  • ✅ Requires <Suspense> wrapper
  • ✅ Use for client-side URL param reading
  • ✅ Combine with useRouter() for updating params
  • ✅ Best for filters, search, pagination
  • ❌ NOT for server components (use searchParams prop instead)

This is the recommended pattern for client-side URL parameter handling in Next.js App Router.

Featured
CodeRabbit
CodeRabbit
AI writes the code. CodeRabbit catches the slop.
Try For Free →
MCP-ready Email SendingMCP-ready Email Sending
MCP-ready Email Sending
Plug Mailtrap into your AI workflow and let it handle the email.
Connect Mailtrap MCP →
Make your agent a DeFi expert
Make your agent a DeFi expert
Agent, run crypto. Access onchain data & trade routes via 1inch.
Install now →
Capacitor - Shared memory for your team’s coding agents.
Capacitor - Shared memory for your team’s coding agents.
Make coding agent sessions - Searchable, Shareable, Vendor-neutral & Scored.
Try For Free →
CodeScene MCP ServerCodeScene MCP Server
CodeScene MCP Server
Your agent targets a perfect 10 Code Health score. Deterministic. Every commit.
Try For Free →
Give your AI the whole web as clean markdownGive your AI the whole web as clean markdown
Give your AI the whole web as clean markdown
Integrate web data into your AI product. One API to scrape website & brand data.
Get API Key Now →
belt - the only tool your agent needs
belt - the only tool your agent needs
belt cli automatically finds the best tools and skills for your agent. image, video, music, tts...
one prompt install →
inference shell
inference shell
create and run specialised agents in minutes
build now →
Categories
Frontend Development
First SeenJun 3, 2026
View on GitHub

More from wsimmonds/claude-nextjs-skills

All 9 skills →
  • Nextjs Server Navigation280
  • Nextjs Dynamic Routes Params278
  • Nextjs Pathname Id Fetch270
  • Nextjs App Router Fundamentals2.2k
  • Nextjs Server Client Components336
  • Vercel Ai Sdk311
  • Nextjs Advanced Routing307
  • Nextjs Client Cookie Pattern298

Recommended

More Frontend Development →
thedivergentai avatar
godot-ui-containers

thedivergentai/gd-agentic-skills

Expert blueprint for responsive UI layouts using Container nodes (HBoxContainer, VBoxContainer, GridContainer, MarginContainer, ScrollContainer, HFlowContainer, SubViewportContainer). Covers size flags, anchors, split containers, virtual_list pooling, stretch_shrink previews, and dynamic layouts. Use when building adaptive interfaces OR implementing responsive menus. Keywords: Container, HBoxContainer, VBoxContainer, GridContainer, HFlowContainer, SubViewportContainer, virtual_list, stretch_shrink, size_flags, EXPAND_FILL, anchors, responsive.
279
464
secondsky avatar
react-best-practices

secondsky/claude-skills

React and Next.js performance optimization guidelines from Vercel Engineering. This skill should be used when writing, reviewing, or refactoring React/Next.js code to ensure optimal performance patterns. Triggers on tasks involving React components, Next.js pages, data fetching, bundle optimization, or performance improvements.
278
204
wsimmonds avatar
nextjs-dynamic-routes-params

wsimmonds/claude-nextjs-skills

Guide for Next.js App Router dynamic routes and pathname parameters. Use when building pages that depend on URL segments (IDs, slugs, nested paths), accessing the `params` prop, or fetching resources by identifier. Helps avoid over-nesting by defaulting to the simplest route structure (e.g., `app/[id]` instead of `app/products/[id]` unless the URL calls for it).
278
109
secondsky avatar
bun-nextjs

secondsky/claude-skills

This skill should be used when the user asks about "Next.js with Bun", "Bun and Next", "running Next.js on Bun", "Next.js development with Bun", "create-next-app with Bun", or building Next.js applications using Bun as the runtime.
277
204
bobmatnyc avatar
nextjs-core

bobmatnyc/claude-mpm-skills

Core Next.js patterns for App Router development including Server Components, Server Actions, route handlers, data fetching, and caching strategies
275
68
wsimmonds avatar
nextjs-pathname-id-fetch

wsimmonds/claude-nextjs-skills

Focused pattern for fetching data using URL parameters in Next.js. Covers creating dynamic routes ([id], [slug]) and accessing route parameters in server components to fetch data from APIs. Use when building pages that display individual items (product pages, blog posts, user profiles) based on a URL parameter. Complements nextjs-dynamic-routes-params with a simplified, common-case pattern.
270
109