Next.js App Router Guide

Next.js 13 introduced the App Router, a new way to handle routing in React applications.

In this guide, I will walk you through the main concepts of the App Router, with practical and interactive examples.

If you haven't used the App Router yet and you also like watching paint dry, then you're in the right place. I'm joking, kind of.

Let's get started.

The Basics

The App Router uses a file-based routing system, where folders represent routes and files define the UI components for those routes:

URL Structure

File Structure

app/page.tsx

Concepts

1. File Conventions

We have some special files that serve specific purposes:

FilePurpose
page.tsx

Defines a route and makes it publicly accessible

layout.tsx

Creates shared layouts for a segment and its children

loading.tsx

Creates loading UI for a segment and its children

error.tsx

Creates error UI for a segment and its children

not-found.tsxCreates UI for not found pages

Example of a basic page.tsx:

import { getLatestPosts } from "@/lib/api";

export default async function BlogPage() {
  const posts = await getLatestPosts();

  return (
    <div>
      <h1>Latest Blog Posts</h1>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

This page.tsx file creates a route for a blog listing page.

2. Layouts

Layouts allow us to create shared UI that wraps multiple pages. They're useful for maintaining consistent navigation, headers, or footers across our app.

Create a root layout:

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <Header />
        <main>{children}</main>
        <Footer />
      </body>
    </html>
  );
}

3. Route Groups

Route groups allow us to organize routes without affecting the URL structure . This is useful for separating our application into logical sections.

app/
├── (marketing)
│   ├── about/
│   │   └── page.tsx
│   └── contact/
│       └── page.tsx
├── (shop)
│   ├── products/
│   │   └── page.tsx
│   └── cart/
│       └── page.tsx
└── layout.tsx

In this example, (marketing) and (shop) are route groups that don't affect the URL path.

4. Dynamic Routes

Dynamic routes allow you to create pages that can match multiple URLs based on dynamic segments. This is useful when you don't know the exact segment names ahead of time and want to create routes from dynamic data.

Convention

A dynamic segment is created by wrapping a folder's name in square brackets: [folderName]

For example:

  • [id]
  • [slug]

File Structure

Here's an example file structure for a blog with dynamic routes:

app/
├── blog/
│   ├── [slug]/
│   │   └── page.tsx
│   └── page.tsx
└── page.tsx

In this structure, [slug] is a dynamic segment that can match any value in the URL.

Example

export default function BlogPost({ params }: { params: { slug: string } }) {
  return (
    <div>
      <h1>Blog Post: {params.slug}</h1>
      {/* Fetch and display the blog post content here */}
    </div>
  );
}

This will match routes like:

/blog/hello  →  { slug: 'hello' }
/blog/world  →  { slug: 'world' }

Fetching Data

In practice, you'd typically fetch data based on the slug.

Example:

import { getBlogPost } from "@/lib/api";

export default async function BlogPost({
  params,
}: {
  params: { slug: string };
}) {
  const post = await getBlogPost(params.slug);

  if (!post) {
    return <div>Post not found</div>;
  }

  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

➡️ Remember: Dynamic segments are passed as the params prop to layout, page, route, and generateMetadata functions.

5. Catch-all and Optional Catch-all Segments

Catch-all segments let you match multiple parts of a URL in one dynamic route. This gives you more flexibility when dealing with nested or unknown paths.

↳ Catch-all Segments

app / shop / [...slug] / page.tsx;
  • Matches one or more segments after the base path
  • Does not match the root path (/shop)
  • Use when you always expect at least one segment

Example use case:

  • /docs/react
  • /docs/react/hooks
  • /docs/react/hooks/useEffect

↳ Optional Catch-all Segments

app / shop / [[...slug]] / page.tsx;
  • Matches zero or more segments after the base path
  • Also matches the root path (/shop)

Example use case:

  • /shop (all products)
  • /shop/clothing
  • /shop/clothing/shirts

Here's how you might handle these routes in your components:

// Catch-all segment: app/docs/[...slug]/page.tsx
export default function DocsPage({ params }: { params: { slug: string[] } }) {
  return <div>Documentation for: {params.slug.join("/")}</div>;
}

// Optional catch-all segment: app/shop/[[...slug]]/page.tsx
export default function ShopPage({ params }: { params: { slug?: string[] } }) {
  if (params.slug === undefined) {
    return <AllProducts />; // Handle /shop
  } else {
    return <FilteredProducts categories={params.slug} />; // Handle nested categories
  }
}

🔐 Optional catch-all segments offer more flexibility by handling both root and nested paths in one component, unlike regular catch-all segments.

Play with the demo below to see how they work:

Select a path:

Matched segments:

{ slug: undefined }

File: app/shop/[[...slug]]/page.tsx

6. Parallel Routes

Parallel routes allow you to render multiple pages simultaneously within the same layout. This is useful for creating UIs with independent navigation, like dashboards or social media feeds. Parallel routes are like having multiple TV channels displayed on your screen at the same time.

It's the ability to show multiple pages or components side by side within the same overall layout.

Key Concepts:
  • Use the @folder convention to create named slots (e.g., @dashboard, @feed)

  • Slots are passed as props to the parent layout
  • Enables conditional rendering based on user roles or other factors

Here's a simple example of how parallel routes can be structured:

app/
├── layout.tsx
├── @dashboard
│   ── page.tsx
│   └── analytics
│       └── page.tsx
└── @feed
    ├── page.tsx
    └── trending
        └── page.tsx

In this structure, you can render different combinations of dashboard and feed content simultaneously.

Loading...

Try switching between different views to see how parallel routes work. Notice how the URL changes and different content is loaded independently for each slot.

Conclusion

We can leverage these concepts to create a more intuitive and maintainable application structure. Try refactoring an existing project or starting a new one using these concepts.

🚀 Happy coding

Resources

Feedback

If you have any questions or suggestions about this guide, feel free to reach out: