← Blog
next.jsreactwebengineering

Next.js App Router: What I Actually Use After One Year

27 February 2026

Next.js App Router: What I Actually Use After One Year

The App Router shipped as the new default in Next.js 13. I've been using it in production since Next.js 15, and I now run this site on Next.js 16. After a year of daily use, I have opinions about what's actually worth learning versus what looks great in a talk but quietly disappears from your codebase after a week.

Here's what I actually use.

Layouts: The Feature That Earns Its Keep

Nested layouts are the single biggest quality-of-life improvement in the App Router. The idea is simple: any layout.tsx wraps all routes beneath it, and layouts don't remount when you navigate between child routes.

On this site, the [locale]/layout.tsx wraps every page with AnimatedBackground and Nav. Before the App Router, keeping a persistent shell around your pages while navigating meant either putting everything in _app.tsx or wrestling with component state. Now it's just a file in the right place.

For apps with authenticated sections, role-based layouts, or multi-step flows, this is transformative. I don't miss the old model at all.

Server Components: The Paradigm Shift That Actually Matters

Server Components are the conceptual core of the App Router, and they take a while to click. The mental model is: components render on the server by default. They can be async. They can fetch data directly — no useEffect, no loading state, no client round-trip.

// This just works. No API route needed.
export default async function BlogPost({ params }) {
  const post = await getPost(params.slug);
  return <article dangerouslySetInnerHTML={{ __html: post.content }} />;
}

The blog on this site uses this pattern. There's no API layer between the page and the filesystem. The post is read, parsed, and rendered in a single server-side pass.

The boundary matters though. Once you add "use client" to a component, it and all its children become client components. Getting that boundary wrong — putting it too high in the tree — is the most common mistake I've made and seen others make. Start server-side, push the "use client" boundary down as far as it can go.

generateMetadata: Finally Sane SEO

Before the App Router, SEO metadata in Next.js meant either a third-party library or manually placing <Head> tags in every page component. generateMetadata replaces all of that with a typed, co-located export.

export async function generateMetadata({ params }) {
  const post = await getPost(params.slug);
  return {
    title: post.title,
    description: post.description,
    openGraph: { /* ... */ },
  };
}

It's async, so you can fetch data for it. It's co-located with the page, so you know exactly where the metadata lives. I haven't touched next-seo since.

The Parts That Bite

The App Router is not without friction. Here's where I've lost time.

Async params. In Next.js 15+, params and searchParams are Promises. You have to await them before accessing values. This is not obvious from the error messages and has bitten me more than once:

// Next.js 15+
export default async function Page({ params }) {
  const { slug } = await params; // don't forget this
}

Middleware naming quirks. Next.js expects the middleware file at middleware.ts. On this site it's proxy.ts, which works because of a specific config setup — but if you clone the repo and wonder why locale detection isn't firing, that's why.

Static vs. dynamic rendering confusion. A single cookies() or headers() call inside a component opts the entire route into dynamic rendering. This can silently kill your static output. The npm run build output shows (static) vs. ƒ (dynamic) per route — I've learned to check it.

What I Skip

Parallel routes and intercepting routes. These are powerful, but I've never needed them. Parallel routes let you render multiple independent route segments in the same layout. Intercepting routes let you show a route in a modal while keeping the underlying URL navigable. Both are legitimate features for complex UIs. For a portfolio site or a focused SaaS app, they're almost certainly overkill.

ISR with revalidate. Incremental Static Regeneration still exists in the App Router via export const revalidate = 60. I've moved toward either fully static or fully dynamic rendering depending on the route. The middle ground — stale data with a timer — introduces a category of bugs I'd rather not debug.

The Honest Verdict

The App Router is worth it. Not because of any single feature, but because layouts, Server Components, and generateMetadata together eliminate an enormous amount of boilerplate that used to be just the cost of doing business in Next.js.

The learning curve is real and the documentation, while improving, still undersells how much your mental model needs to shift. The key insight that took me the longest to internalize: stop thinking in API routes and client-side fetching first. Reach for a Server Component, fetch your data there, and only drop down to the client when you actually need interactivity.

Once that clicks, the rest follows.