NextjsCommon Mistakes & Pitfalls

Common Mistakes & Pitfalls

Most App Router bugs come from a small, recurring set of misunderstandings — importing from the wrong module, misjudging where the server/client boundary actually is, or skipping a check that looks unnecessary because the code "feels local." The table below collects the mistakes that come up again and again, with what actually goes wrong and how to avoid each one.

Mistake

What goes wrong

Importing useRouter from next/router in App Router code

next/router is the Pages Router hook. In app/, it either throws or returns the wrong shape — the App Router hook is useRouter from next/navigation, with a different, smaller API

Misunderstanding the 'use client' boundary

'use client' marks a boundary, not a single component — every component that file imports also becomes part of the client bundle. Adding it to a top-level layout accidentally makes the entire subtree client-rendered

Importing a Server Component into a Client Component file

A Client Component module cannot import and render a Server Component directly — Server Components must be passed down as children/props from a parent that is still on the server side

Forgetting params/searchParams are async in Next.js 15

As of Next.js 15, params and searchParams are Promises in Server Components and Route Handlers — accessing params.id directly without await throws or silently returns a Promise object instead of the value

Trying to set a cookie during a plain render

Cookies can only be set inside a Server Action, a Route Handler, or Middleware — calling cookies().set() from a Server Component render throws, because the response headers are already being streamed

Importing global CSS outside the root layout

Global CSS imports are only allowed in the root app/layout.tsx (or a global stylesheet imported there) — importing a global stylesheet from a nested component throws a build error

Overusing 'use client' on entire pages

Marking a whole page Client Component defeats server rendering for content that never needed it, bloats the client bundle, and delays interactivity for the parts that do

Not validating/authorizing inside Server Actions

A Server Action is a public network endpoint, indistinguishable from an API route to an attacker — skipping server-side auth/validation because "only my form calls this" leaves it open to any client that crafts a matching request

The Next.js 15 async params trap

app/posts/[id]/page.tsx

TSX
// Wrong on Next.js 15+ — params is a Promise, not a plain object
export default function PostPage({ params }: { params: { id: string } }) {
  return <p>{params.id}</p> // renders "[object Promise]"-shaped bugs
}

// Correct
export default async function PostPage({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  return <p>{id}</p>
}
Warning
None of these mistakes are exotic — they are the everyday papercuts of learning where the App Router draws its boundaries. When something behaves strangely, check this list first: wrong useRouter import, an unawaited params, or a 'use client' directive placed one level higher than it needed to be, cover a large share of real-world bug reports.