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 |
|
Misunderstanding the |
|
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 |
Forgetting | As of Next.js 15, |
Trying to set a cookie during a plain render | Cookies can only be set inside a Server Action, a Route Handler, or Middleware — calling |
Importing global CSS outside the root layout | Global CSS imports are only allowed in the root |
Overusing | 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
// 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>
}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.