Next.js 14 App Router: A Complete Guide
David Kim
Next.js 14 App Router: A Complete Guide
Next.js 14 introduces significant improvements to the App Router, making it more powerful and developer-friendly. This guide covers everything from basic concepts to advanced patterns.
What's New in Next.js 14
Performance Improvements
- Turbopack: Faster local development with Rust-based bundler
- Server Actions: Simplified server-side mutations
- Partial Prerendering: Combine static and dynamic content seamlessly
Developer Experience
- Improved Error Handling: Better error messages and debugging
- Enhanced TypeScript Support: Stricter type checking
- Metadata API: Simplified SEO and social sharing setup
App Router Fundamentals
File-Based Routing
The App Router uses a file-system based router where folders define routes:
app/
page.tsx // /
about/
page.tsx // /about
blog/
page.tsx // /blog
[slug]/
page.tsx // /blog/[slug]
Special Files
page.tsx
: UI for a route segmentlayout.tsx
: Shared UI for multiple pagesloading.tsx
: Loading UIerror.tsx
: Error UInot-found.tsx
: 404 UI
Layouts and Templates
Root Layout
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<nav>Navigation</nav>
{children}
<footer>Footer</footer>
</body>
</html>
);
}
Nested Layouts
// app/blog/layout.tsx
export default function BlogLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="blog-layout">
<aside>Blog Sidebar</aside>
<main>{children}</main>
</div>
);
}
Server and Client Components
Server Components (Default)
// app/posts/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts');
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
Client Components
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
Data Fetching Patterns
Parallel Data Fetching
async function getUser(id: string) {
const res = await fetch(`/api/users/${id}`);
return res.json();
}
async function getPosts(userId: string) {
const res = await fetch(`/api/posts?userId=${userId}`);
return res.json();
}
export default async function UserPage({ params }: { params: { id: string } }) {
// These requests run in parallel
const userPromise = getUser(params.id);
const postsPromise = getPosts(params.id);
const [user, posts] = await Promise.all([userPromise, postsPromise]);
return (
<div>
<h1>{user.name}</h1>
<PostsList posts={posts} />
</div>
);
}
Streaming with Suspense
import { Suspense } from 'react';
export default function Page() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<div>Loading analytics...</div>}>
<Analytics />
</Suspense>
<Suspense fallback={<div>Loading posts...</div>}>
<RecentPosts />
</Suspense>
</div>
);
}
Server Actions
Form Handling
// app/contact/page.tsx
async function submitForm(formData: FormData) {
'use server';
const name = formData.get('name') as string;
const email = formData.get('email') as string;
// Process form data
await saveContact({ name, email });
redirect('/thank-you');
}
export default function ContactPage() {
return (
<form action={submitForm}>
<input name="name" placeholder="Name" required />
<input name="email" type="email" placeholder="Email" required />
<button type="submit">Submit</button>
</form>
);
}
Advanced Patterns
Route Groups
app/
(marketing)/
about/
page.tsx // /about
contact/
page.tsx // /contact
(shop)/
products/
page.tsx // /products
Parallel Routes
app/
dashboard/
@analytics/
page.tsx
@team/
page.tsx
layout.tsx
page.tsx
Intercepting Routes
app/
feed/
page.tsx
@modal/
(..)photo/
[id]/
page.tsx // Intercepts /photo/[id]
Migration from Pages Router
Key Differences
- File Structure:
pages/
→app/
- API Routes:
pages/api/
→app/api/
- Data Fetching:
getServerSideProps
→ Server Components - Layouts: HOCs →
layout.tsx
files
Migration Strategy
- Start with new routes in
app/
- Gradually migrate existing pages
- Update data fetching patterns
- Refactor layouts and components
Best Practices
- Use Server Components by Default: Only use Client Components when needed
- Colocate Related Files: Keep components, styles, and tests together
- Optimize Loading States: Use Suspense boundaries strategically
- Handle Errors Gracefully: Implement proper error boundaries
- Leverage Caching: Understand Next.js caching behavior
Conclusion
Next.js 14's App Router represents a significant evolution in React development. By embracing Server Components, improved data fetching, and modern patterns, you can build faster, more maintainable applications. The learning curve is worth the investment for the performance and developer experience benefits.
The App Router isn't just a new routing system—it's a new way of thinking about React applications.