Why App Router?
Since Next.js 13, the App Router has become the default way to build Next.js apps. After using it across multiple projects, here are 5 things I wish I knew from the start.
1. Server Components are the default (and that's great)
Every component in the app/ directory is a Server Component by default. This means:
- No JavaScript shipped to the client
- Direct access to databases and file systems
- Better performance out of the box
Only add "use client" when you actually need interactivity:
// This runs on the server — no "use client" needed
export default async function PostList() {
const posts = await db.posts.findMany();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}2. Use generateStaticParams for static pages
If you have dynamic routes like /blog/[slug], use generateStaticParams to pre-render them at build time:
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}This gives you the performance of a static site with the flexibility of dynamic routes.
3. Metadata API is powerful
Stop using <Head> manually. The Metadata API handles SEO, Open Graph, and Twitter cards:
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.description,
openGraph: {
type: "article",
publishedTime: post.date,
},
};
}4. Server Actions simplify forms
No more API routes for simple form submissions. Server Actions let you call server functions directly:
async function subscribe(formData: FormData) {
"use server";
const email = formData.get("email") as string;
await db.subscribers.create({ data: { email } });
}
export default function Newsletter() {
return (
<form action={subscribe}>
<input name="email" type="email" required />
<button type="submit">Subscribe</button>
</form>
);
}5. Colocate loading and error states
The App Router lets you add loading.tsx and error.tsx files next to your pages. Use them:
app/
blog/
page.tsx
loading.tsx // Shows while page data loads
error.tsx // Catches errors gracefully
[slug]/
page.tsx
This gives you automatic Suspense boundaries without any extra code.
Wrapping up
The App Router has a learning curve, but once it clicks, it's incredibly productive. The key is to embrace server components and let Next.js do the heavy lifting.