Next.js App Router Deep Dive
A practical walkthrough of Next.js App Router patterns — server components, layouts, loading states, and static generation strategies.
Server Components by Default
Next.js App Router flips the mental model: every component is a Server Component unless you opt into 'use client'.
This means:
- You can
awaitdata directly in components - Zero JavaScript shipped for static parts of the page
- Database queries run on the server, not in the browser
Server Components cannot use hooks (useState, useEffect, etc.), event handlers, or browser APIs. When you need interactivity, add 'use client' at the top of the file.
Layouts and Templates
Layout persists across navigation
A layout.tsx wraps child routes and preserves state:
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html>
<body>
<Header />
{children}
<Footer />
</body>
</html>
);
}
Template re-mounts on every navigation
A template.tsx is a layout that re-renders children on each route change — perfect for page transitions and loading states.
Use template.tsx when you need animations on every navigation. Use layout.tsx when you want to persist state.
Static Generation (SSG)
App Router makes SSG the happy path:
// Generate all possible slug values at build time
export async function generateStaticParams() {
const posts = getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}
// Page becomes static for each slug
export default async function PostPage({ params }) {
const { slug } = await params;
const post = getPostBySlug(slug);
// ...
}
No getStaticProps or getStaticPaths — just export generateStaticParams and write your component as an async function.
Loading and Error Boundaries
Each route segment can define its own:
loading.tsx— shown while the page loadserror.tsx— shown when an error occursnot-found.tsx— shown for 404s
Error boundaries in App Router are per-segment. An error in /posts/[slug] won't crash the / page — but it also means you need error files at each level where you want coverage.
Key Takeaways
- Default to Server Components
- Add
'use client'only for interactive islands - Use
generateStaticParamsfor SSG - Leverage layouts, templates, and nested boundaries
- Embrace the file-system routing convention
Mixing Server and Client components without understanding the boundary can lead to unexpected behavior. Client components cannot directly import Server components — they become Client components too. Use children props to maintain the server boundary.
The App Router is different, not harder. Once the mental model clicks, it's a joy to use.