React  

Next.js App Router Explained: The Modern Way to Build Routes

Introduction

So, in the last article, we used the /pages directory in Next.js for routing, and that’s the classic way. But now, Next.js has introduced a brand new routing system called the App Router. It’s super powerful, and once you get the hang of it, it feels kind of easier.

What Is the App Router?

The App Router enables route definition through folder structure within the /app directory, mapping folders to URL paths. This approach supports nested layouts, group routes, and simplifies data fetching in larger applications for better route management.

This newer system gives us:

  • Layouts (shared UI across pages)
  • Nested routing
  • Server and client components
  • Loading states
  • Error handling
  • Better control over rendering (SSR, SSG, etc.)

It’s more flexible than the old way.

Create a new Next.js project using the npx create-next-app@latest command. In Next.js 14, the /app directory is set up by default, and this enables the App Router. No extra configuration is needed.

Static Routes

The App Router still uses file-based routing, but the files look a bit different. We don’t write our page directly as about.js anymore; now we use folders and a page.js file inside each route.

In your project, create folders and files like those shown above. If you are using JavaScript, the page name should be page.js, and if you are using TypeScript, it would be page.tsx. Everything else is the same.

// Folder structure

/app
  page.js         // route - /
  about/
    page.js   // route- /about
  contact/
    page.js        // route  -  /contact

Every route is just a folder now, and each folder must have a page.js (or page.tsx in case of TypeScript) file for it to be a routable page.

In app/about/page.js

// app/about/page.js

export default function About() {
  return (
    <div>
      <h1>About Us</h1>
      <p>This is a static page using the App Router.</p>
    </div>
  );
}

We just hit /about in the browser, and that’s our page. No need to set up routing manually. Next.js handles it based on the folder and file structure.

Dynamic Routes

Dynamic routes still use square brackets like before, but now they’re folders too.

For a dynamic route, our folder structure will look like below -

/app
  blog/
    [id]/
      page.js      // route - /blog/what-is-nextjs

And inside that page.js, we can access route parameters like this-

export default function Post({ params }) {
  return <h1>Blog Post: {params.id}</h1>
}

Layouts

Layouts in App Router are good. We can create a layout.js file at any level to wrap all child pages. Think navbars, sidebars, or persistent headers now no longer duplicate UI everywhere.

Example

/app
  layout.js          // wraps all routes
  dashboard/
    layout.js        // wraps all dashboard pages
    page.js          // /dashboard
    settings/
      page.js        // /dashboard/settings

Each nested layout wraps everything beneath it. It's like nesting layouts inside layouts, and it's super clean.

Here’s a simple layout file-

// app/layout.js
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <header>My App</header>
        <main>{children}</main>
      </body>
    </html>
  )
}

Here, this is a Next.js layout component (app/layout.js) that wraps all pages. It returns an HTML structure with a <header> and <main> tag containing the page content ({children}). This layout is shared across all routes in the Next.js app directory.

Error Handling

We can add an error.js file inside any route folder to catch errors for that route.

/app/dashboard/
  page.js
  error.js     // shows if dashboard fails to load

We do not need to wrap everything in try/catch anymore.

Loading UI

If we want to show a spinner while a page is loading, we can just add a loading.js file next to our page.js.

/app/blog/[id]/
  page.js
  loading.js

In loading.js

export default function Loading() {
  return <p>Loading blog post...</p>;
}

Next.js will automatically show this while the route is loading. We do not need to handle it manually.

Linking Between Routes

It's the same as usual, we use the Link component from next/link -

import Link from 'next/link'

<Link href="/about">About</Link>

This works the same in both routing systems.

Route Groups

Now this one’s super handy. Sometimes, we want to organize our routes into folders, maybe for separating marketing pages vs app pages, but we don’t want that folder name to show up in the URL.

That’s where Route Groups come in. Just wrap the folder name in parentheses-

/app
  (marketing)/  // this will be ignored
    about/
      page.js     // still becomes "/about"

The (marketing) folder is ignored in the final route path. This is useful for grouping routes logically (e.g., marketing vs. dashboard) without changing the URL structure.

It’s especially useful when we want clean routes like /login or /register, but still want to keep them grouped under something like (auth) internally.

Pages Router vs App Router

Here is a side-by-side comparison table for the page router and app router -

Feature Pages Router App Router
File-based routing Yes Yes
Layout support Manual Built-in
Loading/error components Manual or third party Built-in
Server Components No Yes (default)
Route groups Not supported Yes
Recommended by Next.js Legacy (Still works) Future-proof

If you’re starting a new project, go with the App Router; it’s the modern, more powerful way. If you're working with an older app or doing a quick prototype, the Pages Router might still feel quicker and simpler.

Conclusion

The App Router might seem like a lot at first (layouts, folders, loading states, etc.), but once you start using it, it actually simplifies building large-scale applications in the real world. Everything just feels more organized. Routing, layouts, and even data fetching are built right into the structure.

So if you're just getting started, don't worry about mastering it all at once. Try building a small project using /app, and you will get used to it.