nextjs-app-router-routing

Navigation in Next.JS

  • 5 min

In Single Page Application (SPA) architecture with Vite, defining routes was an explicit task. We had a central file (usually App.jsx or main.jsx) where we imported components and assigned them to URLs using <Route path="/about" element={<About />} />.

In Next.js, that configuration layer is replaced by a folder convention.

Next.js adopts the convention over configuration paradigm. We don’t write a routes file; our folder structure defines the routes.

If you create a folder, you create a route. If you delete the folder, you delete the route. It’s that straightforward, and once you get used to it, it’s hard to go back.

Defining Static Routes

The App Router works based on a golden rule:

A folder in src/app defines a route segment. But the route is only publicly accessible if that folder contains a special file called page.tsx.

Let’s see how the file structure translates to URLs in the browser:

  • src/app/page.tsxmyapp.com/ (Home)
  • src/app/contact/page.tsxmyapp.com/contact
  • src/app/blog/page.tsxmyapp.com/blog
  • src/app/blog/first-post/page.tsxmyapp.com/blog/first-post

If you create a folder src/app/components, but don’t put a page.tsx inside it, that route won’t exist. This allows us to have folders for organizing code (components, utilities, styles) alongside pages without fear of them being accidentally exposed as URLs.

Layouts and Nesting

This is where Next.js significantly simplifies the architecture. Think about a “Dashboard” section (/dashboard) that has its own Sidebar, different from the Home page’s. And within the Dashboard, you have /dashboard/settings and /dashboard/profile.

In React Router, managing the Sidebar to not unmount when navigating between settings and profile was complex. In Next.js, this is native thanks to Nested Layouts.

We can create a layout.tsx file at any level of the folder hierarchy.

src/app/
├── layout.tsx      (Root Layout: html, body, Global Navbar)
├── page.tsx        (Home)
└── dashboard/
    ├── layout.tsx  (Dashboard Layout: Sidebar)
    ├── page.tsx    (Dashboard main view)
    └── settings/
        └── page.tsx (Settings view)

Copied!

When you enter /dashboard/settings:

Next.js renders the Root Layout.

Inside that, it renders the Dashboard Layout.

Inside that, it renders the Settings Page.

// src/app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <div className="flex">
      <aside className="w-64 bg-gray-100 p-4">
        {/* This Sidebar is persistent */}
        <nav>...</nav>
      </aside>
      
      <section className="flex-1 p-8">
        {/* Child pages (settings, profile...) are injected here */}
        {children}
      </section>
    </div>
  );
}

Copied!

When navigating between pages that share the same Layout, the Layout does not re-render. Its state is preserved (sidebar scroll position, text in search inputs, etc.).

Dynamic Routes

What if we want a route to view a product detail, where the ID changes? (/products/1, /products/nike-shoe). We can’t create infinite folders.

For this, we use square brackets [] in the folder name.

For example:

src/app/products/[id]/page.tsx
Copied!

The name we put inside the brackets (in this case id) will be the name of the parameter we receive in our component.

// src/app/products/[id]/page.tsx

// Define the type for props
interface Props {
  params: Promise<{
    id: string;
  }>;
}

export default async function ProductDetail({ params }: Props) {
  const { id } = await params;

  return (
    <div>
      <h1>Viewing product with ID: {id}</h1>
      {/* We would fetch from the database using this ID */}
    </div>
  );
}

Copied!

Thus, any URL matching the pattern /products/whatever will render this component.

Finally, we need to move between these routes. Remember, we cannot use the standard HTML <a href="/about"> tag.

An <a> tag causes a full page reload (Hard Refresh), losing React state and downloading all the JavaScript again.

Next.js provides us with the Link component, which extends the <a> tag but performs navigation on the client (Client-Side Navigation).

import Link from 'next/link';

export default function Navbar() {
  return (
    <nav className="flex gap-4">
      <Link href="/">Home</Link>
      
      <Link href="/dashboard" className="text-blue-500">
        Go to Dashboard
      </Link>
      
      <Link href="/products/123">
        View Product 123
      </Link>
    </nav>
  );
}

Copied!