We use tracking cookies to understand how you use the product and help us improve it. For more information on how we store cookies, read our  privacy policy.

Routing

Next.js App Router structure and navigation

Next.js 16 App Router uses file-based routing with powerful features like layouts, loading states, and Cache Components mode.

File-Based Routing

page.tsx
layout.tsx

URL Mapping:

  • app/page.tsx/
  • app/blog/page.tsx/blog
  • app/blog/[slug]/page.tsx/blog/my-post

Special Files

FilePurpose
page.tsxRoute UI
layout.tsxShared wrapper
loading.tsxLoading state
error.tsxError boundary
not-found.tsx404 page
route.tsAPI endpoint

Route Groups

Use (name) to organize without affecting URLs.

Route groups structure
app/
├── (auth)/
   ├── sign-in/page.tsx /sign-in
   └── layout.tsx            (auth layout)
├── (base)/
   ├── blog/page.tsx /blog
   └── layout.tsx            (main layout)
└── layout.tsx                (root layout)

Route groups like (auth) don't appear in URLs. They're for organization and applying different layouts.

Layouts

Layouts wrap pages and persist across navigation.

app/layout.tsx
// app/layout.tsx (required)
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

// app/(base)/layout.tsx (nested)
export default function BaseLayout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <Navigation />
      <main>{children}</main>
      <Footer />
    </>
  );
}

Dynamic Routes

Single Segment

app/blog/[slug]/page.tsx
// app/blog/[slug]/page.tsx
export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPostBySlug(params.slug);
  return <article>{post.title}</article>;
}

// Generate static pages
export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map((post) => ({ slug: post.slug }));
}

Catch-All

app/docs/[...slug]/page.tsx
// app/docs/[...slug]/page.tsx
export default async function DocsPage({ params }: { params: { slug: string[] } }) {
  const page = await getDocPage(params.slug);
  return <article>{page.content}</article>;
}

Examples:

  • /docs/getting-startedslug = ["getting-started"]
  • /docs/core/installationslug = ["core", "installation"]

Optional Catch-All

app/shop/[[...categories]]/page.tsx
// app/shop/[[...categories]]/page.tsx
export default function ShopPage({ params }: { params: { categories?: string[] } }) {
  const categories = params.categories || [];
  return <ProductList categories={categories} />;
}

Loading States

app/dashboard/loading.tsx
// app/dashboard/loading.tsx
export default function Loading() {
  return <div className="animate-spin">Loading...</div>;
}

Automatically wrapped in Suspense. Shows while page.tsx loads.

Error Boundaries

app/dashboard/error.tsx
// app/dashboard/error.tsx
'use client';

export default function Error({ error, reset }: { error: Error; reset: () => void }) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Try again</button>
    </div>
  );
}
Link component usage
import Link from 'next/link';

<Link href="/blog">Blog</Link>
<Link href="/docs" prefetch={false}>Docs</Link>

useRouter Hook

useRouter hook
'use client';
import { useRouter } from 'next/navigation';

export function LoginForm() {
  const router = useRouter();
  
  const handleSubmit = async () => {
    await login();
    router.push('/dashboard');
  };
}

redirect Function

redirect function
import { redirect } from 'next/navigation';

export default async function ProfilePage() {
  const user = await getCurrentUser();
  if (!user) redirect('/sign-in');
  return <Profile user={user} />;
}

API Routes

app/api/users/route.ts
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function GET(req: NextRequest) {
  const users = await prisma.user.findMany();
  return NextResponse.json(users);
}

export async function POST(req: NextRequest) {
  const body = await req.json();
  const user = await prisma.user.create({ data: body });
  return NextResponse.json(user, { status: 201 });
}

Dynamic API Routes

app/api/users/[id]/route.ts
// app/api/users/[id]/route.ts
export async function GET(
  req: NextRequest,
  { params }: { params: { id: string } }
) {
  const user = await prisma.user.findUnique({ where: { id: params.id } });
  if (!user) {
    return NextResponse.json({ error: 'Not found' }, { status: 404 });
  }
  return NextResponse.json(user);
}

Middleware

middleware.ts
// middleware.ts
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isPublicRoute = createRouteMatcher(['/', '/sign-in(.*)', '/blog(.*)']);

export default clerkMiddleware(async (auth, req) => {
  if (!isPublicRoute(req)) {
    await auth.protect();
  }
});

Quick Reference

Dynamic Routes: [slug] (single), [...slug] (catch-all), [[...slug]] (optional)
Route Groups: (name) - organize without affecting URLs
Navigation: <Link> (client), router.push() (programmatic), redirect() (server)
Special Files: page.tsx, layout.tsx, loading.tsx, error.tsx, route.ts

How is this guide ?

Last updated on