日本語

App Router pitfalls: common Next.js mistakes and practical ways to avoid them

alt

Have you migrated to the Next.js App Router and felt that things still behaved unexpectedly, even though the build passed?

The App Router is built on a different design philosophy from the Pages Router. Core concepts such as Server Components, caching strategy, and layout structure have all changed. The official docs are solid, but once you apply these ideas in production, unexpected pitfalls are common.

This article organizes the most common App Router pain points and explains the root causes, trade-offs, and practical fixes you can apply.


What changed with the App Router?

The App Router is the routing model introduced in Next.js 13 and later.

Key features

  • Server Components are the default
  • Hierarchical layout with layout.tsx
  • Explicit cache control for fetch
  • Streaming support
  • Route Handlers for API endpoints

Benefits

  • Better performance by reducing unnecessary client-side JavaScript
  • Reusable shared layouts
  • More consistent data-fetching patterns

Trade-offs to watch

  • Cache behavior can feel non-intuitive at first
  • You need a clear mental model of the Server/Client boundary
  • Some existing libraries may require adaptation

In practice, many teams hit the same questions:
"Why is this not updating?"
"Why can't I use useEffect here?"


Common pitfalls with Server Components

In the App Router, components are Server Components by default.

Common issues

  • useState and useEffect are unavailable
  • Referencing window causes errors
  • onClick does not work as expected

Why it happens

Server Components render on the server and do not include client-side runtime behavior.

How to fix it

  • Put "use client" at the beginning when interaction is required.
  • Separate Server and Client responsibilities.
  • Move as much logic as possible to the server side.

Practical note

Using "use client" everywhere "just to make it work" usually hurts performance. Client components should stay minimal and intentional.

For a deeper design guide on Server/Client boundaries, see:
/dev/server-client-boundary-guide.


Why fetch caching causes confusion

In the App Router, fetch is cached by default.

Common symptoms

  • Data is not updated
  • ISR behavior is not what you expected
  • Revalidation appears to "do nothing"

Cache-control options

  • cache: "no-store"
  • next: { revalidate: 60 }
  • revalidateTag()
  • revalidatePath()

Benefits

  • Improved performance
  • CDN optimization

Downsides

  • Slower reflection of content updates
  • More confusion during development and debugging

Practical strategy

  • Use no-store for admin or dashboard pages
  • Use revalidate for article-type content
  • Use tag-based invalidation for dynamic data (revalidateTag)

In Vercel deployments, clarity matters even more because App Router caching can interact with Edge caching.

For details on cache design and SEO impact, see:
/ai/nextjs-cache-seo-strategy.


Pitfalls in layout.tsx design

One of the most powerful App Router features is hierarchical layouts.

Common issues

  • Layout does not rerender on child pages
  • Metadata is not overwritten
  • Unclear scope of global CSS influence

Cause

Layouts are reused after initial render, so state and responsibility boundaries must be designed carefully.

Countermeasures

  • Avoid keeping state directly in shared layouts
  • Keep page-specific data logic in page.tsx
  • Define metadata clearly with explicit exports

Practical insight

Overloaded layouts reduce maintainability quickly. Strict separation of concerns improves readability and reuse.


Route Handlers: common pitfalls

Route Handlers replace API Routes in the App Router model.

Frequent stumbling points

  • POST does not work
  • Request body parsing is different
  • Runtime differences between Edge and Node.js

What to keep in mind

  • Request follows the Web Standard API
  • You must parse the body with await request.json()
  • Some Node APIs are unavailable in Edge runtime

Practical actions

  • For Node-dependent processing, declare runtime = "nodejs"
  • Decide early whether each endpoint should run on Edge or Node.js

Misconceptions about dynamic routing and generateStaticParams

Common issues

  • 404s
  • No static generation
  • Error at build time

What you need to understand

  • generateStaticParams runs only at build time
  • Fully dynamic rendering and static generation are different strategies with different trade-offs

Countermeasures

  • Use SSR for frequently updated content
  • Use static generation + revalidate for stable content

Summary: operating App Router reliably in production

Most App Router issues happen when teams keep old Pages Router assumptions without adapting to the new architecture.

Key takeaways

  • Define clear Server/Client boundaries
  • Set caching strategy during design, not after bugs appear
  • Keep layouts lean
  • Understand Route Handler runtime behavior
  • Do not mix up static and dynamic rendering models

Actions you can take now

  1. Audit fetch usage and cache settings in existing pages
  2. Remove unnecessary "use client" directives
  3. Review layout.tsx responsibilities and reduce shared-state risk

The App Router can feel difficult at first, but it becomes a powerful and predictable architecture once the design is structured properly.

A focused architecture review now will pay off with better performance, cleaner code, and more stable long-term operation.

Related posts