Skip to content

Next.js Dynamic Server Error: Using searchParams in Route Handlers

Problem Statement

When building Next.js applications (version 13+), developers may encounter an error during production builds:

none
[Error]: Dynamic server usage: Page couldn't be rendered statically because it used `nextUrl.searchParams`

This error typically occurs when:

  • Using nextUrl.searchParams or new URL(request.url) in API route handlers
  • Wrapping dynamic operations in try/catch blocks that swallow Next.js internal errors
  • The code works in development (npm run dev) but fails during production builds (npm run build)

The core issue arises because Next.js attempts to statically generate routes by default, but cannot do so when runtime data (like URL parameters) is accessed.

Why This Error Occurs

Next.js route handlers are static by default (until Next.js 15) meaning:

  1. Next.js tries to render routes at build time for performance optimization
  2. When runtime data (headers, URL searchParams) is detected, a DynamicServerError is thrown internally
  3. This error signals Next.js to automatically switch to dynamic rendering

The critical problem happens when this internal error is caught in custom try/catch blocks:

javascript
export const GET = async (request) => {
  try {
    // Dynamic usage within try block causes internal error ➡️ 
    const params = request.nextUrl.searchParams;
    // ... but we catch ALL errors below
  } catch (error) {
    // Custom error handling prevents Next.js from detecting dynamic usage ✖️
    return NextResponse.json({ error: 'Internal error' });
  }
}

By catching the DynamicServerError, you prevent Next.js from switching to dynamic rendering mode, causing the build to fail.

Optimal Solutions

Solution 1: Move Dynamic Operations Outside try/catch

Move any dynamic data access before your try block:

javascript
export const GET = async (request) => {
  // Access dynamic data FIRST
  const searchParams = request.nextUrl.searchParams;
  const email = searchParams.get('email');

  try {
    // Proceed with your logic
    const users = await getUsers({ email });
    return NextResponse.json(users);
  } catch (error) {
    // Handle non-Next.js errors
    return NextResponse.json({ error: 'Internal Server Error' });
  }
};

Solution 2: Force Dynamic Rendering Explicitly

Add to your API route file:

javascript
export const dynamic = 'force-dynamic';

This approach:

  • Explicitly marks the route as dynamic
  • Eliminates the static generation attempt
  • Works in both app and page routers
javascript
export const dynamic = 'force-dynamic'; // Add this export

export const GET = async (request) => {
  const searchParams = request.nextUrl.searchParams;
  // ... rest of code
};

Solution 3: Rethrow Next.js Errors (Advanced)

For situations where you must keep dynamic operations inside try/catch:

javascript
import { isDynamicServerError } from 'next/dist/client/components/hooks-server-context';

export const GET = async (request) => {
  try {
    const searchParams = request.nextUrl.searchParams;
    // ... dynamic operations
  } catch (error) {
    if (isDynamicServerError(error)) {
      throw error; // Let Next.js handle its own errors
    }
    return NextResponse.json({ error: 'Custom error' });
  }
};

Note for Next.js 15+: Use unstable_rethrow:

javascript
import { unstable_rethrow } from 'next/dist/client/components/server-hooks';

try {
  const searchParams = new URL(request.url).searchParams;
} catch (error) {
  unstable_rethrow(error);
  // Handle other errors below...
}

When to Use Each Approach

ApproachBest ForCaveats
External accessSimple routesDon't use if parameter access must be in try block
force-dynamicRapid fixing, API routesDisables static optimization
Rethrowing errorsComplex error handlingRequires Next.js version 14+

Best Practices for SearchParams

When working with URL parameters:

  1. Avoid creating URL objects excessively: Reuse a single instance if possible
  2. Validate parameters early: Ensure parameters meet expectations before further processing
  3. Use TypeScript for safety: Type coercion for URL parameters:
typescript
const id = searchParams.get('id') ? Number(searchParams.get('id')) : undefined;
  1. Combine the external access method with force-dynamic for critical API routes:
javascript
export const dynamic = 'force-dynamic';

export const GET = async (request: Request) => {
  const { searchParams } = new URL(request.url);
  // ... safe processing
};

Why Development vs. Production Differ

  • Development mode: Runs in Node.js without static generation optimizations
  • Production build: Attempts static optimization during next build
  • Dynamic operations require explicit handling to prevent failed builds

WARNING

Avoid this anti-pattern:

javascript
export const GET = async (request) => {
  try {
    // ❌ Accesses dynamic data inside try block
    const url = new URL(request.url);
  } catch (error) {
    // ❌ Swallows Next.js dynamic detection error
    return handleError(error);
  }
}

By following these patterns, you resolve the "Dynamic server usage" error while maintaining both development flexibility and production optimization.