The App Router is now the default in Next.js and has been production-ready long enough that clear patterns have emerged. Some approaches work well. Others cause real problems. This guide covers what to use, what to avoid, and why.
App Router vs Pages Router: The Current State
App Router is the default for all new Next.js projects. Pages Router is still fully supported and will not be removed anytime soon. If you have an existing Pages Router app that works, you do not need to migrate unless you specifically want App Router features.
For new projects, App Router is the correct choice. The data fetching model is cleaner, Server Components reduce client bundle size, and the layout system is more powerful. The learning curve is real but manageable.
Mixing both routers in the same project is possible but creates friction. The two routers have different conventions, different data fetching patterns, and different caching behaviors. If you are building a new project, pick one and stay with it.
Patterns That Work Well
Server Components for data fetching is the biggest win the App Router gives you. Instead of fetching in useEffect (which causes loading spinners and waterfall requests) or in getServerSideProps (which blocks the entire page), Server Components let you fetch data directly in the component that needs it.
// Server Component -- runs on server, no bundle cost
async function ProjectList({ orgId }: { orgId: string }) {
const projects = await db.collection("projects").find({ org_id: orgId }).toArray();
return <ul>{projects.map(p => <ProjectCard key={p._id} project={p} />)}</ul>;
}
This component never ships to the client. The database query runs on the server. No API route needed. No loading state needed for the initial render.
Route Handlers replace Pages Router API routes. Use them for endpoints that external services call (webhooks), for file uploads, for cases where you need full control over the response headers, or for endpoints consumed by client components via fetch. They are clean and straightforward.
Server Actions are best for form mutations. A form submits, the action runs on the server, and the page revalidates. No separate API route, no manual fetch call from the client. This works well for create, update, and delete operations on forms.
Parallel Routes solve a real problem: showing two different sections of a page that each have their own loading and error states. A dashboard with a sidebar and a main panel, where each section loads independently, is a good use case. The @slot convention takes getting used to but the result is clean.
Intercepting Routes are useful for modal patterns. You can show a photo in a modal when navigating from a gallery, then show the full photo page when navigating directly to it. This is a niche use case but solves it elegantly.