
Avoid this hard to debug Next.js tree-shaking quirk, by learning how to structure your Route Handlers
When working with Next.js Route Handlers in App Router, you might have stumbled upon a build error which looks something like this:
[Error]: Dynamic server usage: Page couldn't be rendered statically because it used `nextUrl.searchParams`.This means that Next.js thinks the code needs request-time data and can’t be statically rendered, which often happens in pages/components, but it’s less common in Route Handlers.
A simple fix is to ensure that your route handler is not imported in a page component. If you share any libraries between your Route Handlers and Pages, Layouts or regular components, these will be marked by static analysis as dynamic. To avoid the issue you can opt-in to make your affected Route Handler dynamic by adding the following line:
export const dynamic = 'force-dynamic'As it’s often the case, static analysis during build time can mistakenly infer dynamic usage of your route, even though it seems perfectly static. It’s a tree-shaking quirk in Next.js, which may force you to move usage of request.nextUrl utilities outside the try...catch block.
Here’s an example on how to structure your Route Handlers, to avoid this issue, which is especially handy for building API Routes:
// app/api/search/route.ts
import { NextRequest, NextResponse } from 'next/server';
// If needed: export const dynamic = 'force-dynamic';
export async function GET(req: NextRequest) {
// Parse and validate as early as possible
const query = (req.nextUrl.searchParams.get('query') ?? '').trim();
if (!query) {
return NextResponse.json({ error: 'Query must not be empty' }, { status: 400 });
}
try {
const results = await performSearch(query); // pure helper that takes data, not req
return NextResponse.json({ results }, { status: 200 });
} catch {
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}
// lib/search.ts (pure)
export async function performSearch(query: string) {
// No Next APIs here; just use the input
return [`You searched for: ${query}`];
}This separation avoids accidental dynamic mark for pages that might import from lib/search.ts, and keeps all request-specific reading inside the handler.