Why the same mistakes keep returning
App Router gives teams more expressive server and client boundaries, but that flexibility also creates more ways to choose the wrong one. The most common problems are not framework mysteries. They are ordinary ownership mistakes disguised by new primitives.
Generated code repeats them because it optimizes for code that compiles, not code that preserves caching intent, server authority, and small interactive surfaces.
The seven mistakes worth remembering
These patterns show up often enough that they deserve permanent review attention.
- Fetching on the client by default when the data could stay server-side.
- Adding use client too high in the tree because one child needs interactivity.
- Mixing page-level data fetching with browser-only storage or DOM assumptions.
- Hiding mutation and revalidation intent inside ad hoc client fetch calls.
- Passing non-serializable props across the server-client boundary.
- Ignoring caching behavior and then being surprised by stale or over-fetched data.
- Treating every route like an isolated screen instead of part of one data model.
How to review data fetching faster
Ask three questions first: where should this data live, who is allowed to mutate it, and what caching behavior does the user actually need? Those questions expose most App Router fetching problems without needing a framework debate.
If the answers are still vague after reading the diff, the code is probably doing too much in one place.
A better default posture
Default to server-side fetching, then move only the smallest necessary interaction surface to the client. That posture keeps data closer to authority and makes client code earn its existence.
App Router feels calmer when the team treats client rendering as the exception it should justify, not the baseline it casually expands.