Background Dots
Published on

Building Dynamic Websites with Next.js App Router

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.