Introduction to the App Router
Next.js 13 introduced a new App Router built on React Server Components. This new paradigm offers significant improvements in how we structure and build Next.js applications, focusing on server-first rendering and client components for interactivity.
Key Features of the App Router
Nested Layouts
One of the most powerful features is the ability to create nested layouts that preserve state across page navigations:
// app/layout.js
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<header>My Website</header>
<main>{children}</main>
<footer>© 2023</footer>
</body>
</html>
)
}
// app/blog/layout.js
export default function BlogLayout({ children }) {
return (
<div>
<aside>Blog Categories</aside>
<section>{children}</section>
</div>
)
}
Server Components
React Server Components render on the server and can access resources like databases directly:
// app/users/page.js
// This entire component runs on the server
async function UsersPage() {
const users = await db.users.findMany() // Direct database access
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
Client Components
For interactive UI, you can opt into client-side rendering:
// app/counter.js
'use client' // This directive marks this as a Client Component
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
Loading States
The App Router provides a simple way to show loading states:
// app/products/loading.js
export default function Loading() {
return <div>Loading products...</div>
}
Error Handling
Error boundaries are built into the routing:
// app/dashboard/error.js
'use client' // Error components must be Client Components
export default function Error({ error, reset }) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
)
}
Data Fetching in the App Router
Data fetching has been simplified with async/await syntax:
// app/products/page.js
async function ProductsPage() {
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 60 },
}).then((res) => res.json())
return (
<div>
<h1>Products</h1>
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</div>
)
}
Route Handlers
API routes are now called Route Handlers in the App Router:
// app/api/users/route.js
export async function GET() {
const users = await db.users.findMany()
return Response.json(users)
}
export async function POST(request) {
const { name, email } = await request.json()
const user = await db.users.create({ data: { name, email } })
return Response.json(user)
}
Conclusion
The App Router in Next.js represents a significant evolution in how we build web applications. By embracing React Server Components, nested layouts, and simplified data fetching, developers can create more performant applications with improved user experiences.
In our next article, we'll explore more advanced patterns and optimizations for production-ready Next.js applications.