Next.js 13 introduced the App Router in 2022, and it reached a production-stable state in Next.js 13.4 (May 2023). Since then I've used it on every client project at TrieTech, replacing the Pages Router entirely. After a year of real-world usage — including large ERPs, social platforms, and e-commerce systems — here's a frank assessment of where it shines and where it still trips you up.
React Server Components Are the Headline Feature
The biggest conceptual shift in the App Router is React Server Components (RSC). By default, every component inside `app/` renders on the server and ships zero JavaScript to the client. This completely changes how you think about data fetching — instead of `useEffect` + loading states, you fetch data directly inside async server components.
// app/projects/page.tsx — fetches on the server, no client JS needed
export default async function ProjectsPage() {
const projects = await db.projects.findMany({ orderBy: { createdAt: "desc" } });
return (
<ul>
{projects.map((p) => (
<li key={p.id}>{p.name}</li>
))}
</ul>
);
}The elimination of client-side data fetching waterfalls is a genuine performance win. First Contentful Paint is faster because the browser receives fully-rendered HTML, not a loading skeleton waiting on an API call.
Nested Layouts Eliminate Boilerplate
Before the App Router, sharing layouts across pages required wrapping every `_app.tsx` export or composing layout components manually. Nested layouts in the App Router make this declarative. A `layout.tsx` at any level of the folder tree automatically wraps all children routes.
app/
layout.tsx ← root layout (html, body, providers)
(marketing)/
layout.tsx ← marketing shell (navbar, footer)
page.tsx ← /
about/page.tsx ← /about
(app)/
layout.tsx ← authenticated shell (sidebar, session check)
dashboard/
page.tsx ← /dashboardRoute groups (folders wrapped in parentheses) let you apply different layouts to different route subtrees without affecting the URL. This is invaluable when you have a public marketing site and a separate authenticated application under the same domain.
Streaming SSR with Suspense
When a server component awaits slow data, it can stream HTML incrementally using `<Suspense>`. The browser starts rendering the page shell immediately while waiting for the slow part to resolve. This gives you a perceived performance improvement without any client-side JavaScript.
import { Suspense } from "react";
import { SlowDataTable } from "./slow-data-table";
export default function Page() {
return (
<section>
<h1>Dashboard</h1>
{/* Streams the table in once the slow query resolves */}
<Suspense fallback={<TableSkeleton />}>
<SlowDataTable />
</Suspense>
</section>
);
}Server Actions Cut the API Layer
Server Actions (stable since Next.js 14) let you call server-side functions directly from client components using the `"use server"` directive — no API route required. For straightforward mutations like form submissions, this reduces boilerplate significantly.
"use server";
export async function createProject(formData: FormData) {
const name = formData.get("name") as string;
await db.projects.create({ data: { name } });
revalidatePath("/projects");
}Tip
Server Actions are not a replacement for a proper API layer in complex apps. For any endpoint that needs to be consumed externally (mobile apps, third-party integrations), stick with Route Handlers.
The Rough Edges
- The `use client` / `use server` boundary takes time to internalise. Passing non-serialisable props (functions, class instances) across the boundary still causes confusing errors.
- Caching behaviour changed significantly between Next.js 13, 14, and 15. In Next.js 15, `fetch` requests are no longer cached by default — a breaking change from prior versions. Always pin your Next.js version until you've read the upgrade guide.
- Third-party libraries that rely on React context or browser APIs must be placed behind `use client`, and wrapping them in provider components adds indirection.
- The dev server is noticeably slower than the Pages Router equivalentfor large apps, though the production build performance is unaffected.
Verdict
The App Router is the right architectural direction. Collocating data fetching with the component that needs it, eliminating redundant client-side JavaScript, and making layouts composable are all genuine improvements. The learning curve is real but front-loaded — once the RSC mental model clicks, development velocity increases. Every new project I start uses the App Router.