Home
NextJS
Next.js: Routing, Error Handling & Loading States
December 08, 2025
2 min

Table Of Contents

01
1. File-Based Routing: The Basics
02
2. Nested Routes
03
3. Dynamic Routes
04
4. Linking Between Pages
05
5. Layouts: Shared UI Across Pages
06
6. Route Groups
07
7. Routing Improvements in Next.js 16
08
8. Handling Errors with error.tsx
09
9. Loading States with loading.tsx
10
10. Forbidden & Unauthorized Pages (Next.js 16)
11
Conclusion

Now that you understand the basics of the App Router, it’s time to dive deeper into one of the most powerful aspects of Next.js: Routing. Unlike traditional React—where routing requires libraries like React Router—Next.js uses file-based routing, which is simpler, faster, and more intuitive.

In this guide, you’ll learn:

  • How routing works in Next.js
  • How to create nested and dynamic routes
  • How layouts influence routing
  • How to use route groups
  • How error handling works
  • How loading states work
  • What’s new in Next.js 16 for routing

Let’s get started.


1. File-Based Routing: The Basics

Next.js maps your folder structure directly to URLs.

Inside the app/ folder:

  • Every folder = a route
  • Every page.tsx = page content
  • Folder names become URL segments

For example:

app/
├── page.tsx → '/'
├── about/
│ └── page.tsx → '/about'

To create a new route like /about, just:

  1. Create a folder named about
  2. Add a page.tsx inside it
  3. Export a React component

Next.js automatically makes it available at:

http://localhost:3000/about

No configuration, no routers, no boilerplate.


2. Nested Routes

Let’s imagine you’re building an admin dashboard with routes like:

  • /dashboard/users
  • /dashboard/analytics

To do this:

app/
├── dashboard/
│ ├── users/
│ │ └── page.tsx
│ ├── analytics/
│ │ └── page.tsx

Each folder represents a nested route. It’s simple, clean, and extremely scalable.


3. Dynamic Routes

What if you want a route like:

  • /dashboard/users/1
  • /dashboard/users/2
  • /dashboard/users/92301

You don’t create a folder for every user.

Instead, create a dynamic folder:

app/
├── dashboard/
│ └── users/
│ └── [id]/
│ └── page.tsx

The [id] folder tells Next.js:

“This part of the URL is dynamic.”

Inside page.tsx, you can access the ID using params.

Example:

export default async function UserPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
return <h1>Showing details for user {id}</h1>;
}

This is how you build dynamic, data-driven pages in Next.js.


4. Linking Between Pages

Use the built-in Link component:

import Link from "next/link";
<Link href="/dashboard/users/1">User 1</Link>

By default, Next.js:

  • Prefetches links
  • Improves transitions
  • Speeds up navigation

5. Layouts: Shared UI Across Pages

layout.tsx files let you share UI across multiple routes.

Example root layout:

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<nav>Navbar</nav>
{children}
<footer>Footer</footer>
</body>
</html>
);
}

Whatever you put in a layout:

  • Appears on every route under it
  • Can be overridden by child layouts

Example: Dashboard-specific layout

app/
├── dashboard/
│ └── layout.tsx

Inside this layout, you may add a dashboard navbar:

<p>Dashboard Navbar</p>
{children}

This layout applies only to dashboard pages.


6. Route Groups

Now suppose you want:

  • One layout for your public pages (home, about)
  • Another layout for dashboard pages

But you don’t want the folder names to appear in the URL.

This is where route groups help.

Create folders wrapped in parentheses:

app/
├── (root)/
│ ├── page.tsx
│ ├── about/
│ └── layout.tsx ← public layout
├── (dashboard)/
│ ├── dashboard/
│ │ ├── users/
│ │ ├── analytics/
│ │ └── layout.tsx ← dashboard layout

The parentheses tell Next.js:

“Use this folder for organization and layouts, but DON’T add it to the URL.”

This lets you structure your project however you want without affecting routing.


7. Routing Improvements in Next.js 16

Next.js 16 introduced:

Layout Deduplication

If multiple prefetches share the same layout…

Next.js downloads that layout only once.

This reduces bandwidth and speeds up navigation automatically.

No code changes required.


8. Handling Errors with error.tsx

Next.js provides built-in error boundaries.

Just create an error.tsx inside any route folder:

app/
├── (root)/
│ ├── error.tsx

The error file must be a client component:

"use client";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
);
}

Error hierarchy rules:

  • The closest error.tsx handles the error
  • Errors “bubble up” until they reach one
  • Global errors can be handled by adding global-error.tsx

This keeps your app stable and user-friendly.


9. Loading States with loading.tsx

To show a loading spinner or skeleton UI while data loads, create:

app/
└── loading.tsx

Example:

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

This automatically displays anytime a route’s data is being fetched.


10. Forbidden & Unauthorized Pages (Next.js 16)

Next.js 16 adds two special files:

  • forbidden.tsx
  • unauthorized.tsx

Use these when:

  • User lacks permissions
  • User isn’t signed in
  • You need to prevent access elegantly

Instead of an error or redirect, you can provide a clear message or UI.


Conclusion

You now understand:

✔ Page routing ✔ Nested routing ✔ Dynamic routing with params ✔ Layouts (root + nested) ✔ Route groups ✔ Navigation with <Link> ✔ Error handling ✔ Loading states ✔ Next.js 16 routing improvements

This is the foundation of how real-world Next.js applications are structured and scaled.


Tags

#tailwindcss

Share

Related Posts

Crash Course
Next.js: Build Adapters API
December 13, 2025
2 min
© 2025, All Rights Reserved.
Powered By

Social Media

githublinkedinyoutube