Next.js Routing Cheat Sheet

Next.js Routing Cheatsheet

File-Based Routing

Basic Route Structure

app/
├── page.js              # / (home)
├── about/
│   └── page.js          # /about
├── blog/
│   ├── page.js          # /blog
│   └── [slug]/
│       └── page.js      # /blog/[slug]
├── products/
│   ├── page.js          # /products
│   └── [category]/
│       └── [id]/
│           └── page.js  # /products/[category]/[id]
└── api/
    ├── users/
    │   └── route.js     # /api/users
    └── posts/
        └── [id]/
            └── route.js # /api/posts/[id]

Basic Pages

// app/page.js (Home page)
export default function HomePage() {
    return (
        <div>
            <h1>Welcome to our site</h1>
            <p>This is the home page</p>
        </div>
    );
}

// app/about/page.js
export default function AboutPage() {
    return (
        <div>
            <h1>About Us</h1>
            <p>Learn more about our company</p>
        </div>
    );
}

Dynamic Routes

Single Dynamic Segment

// app/blog/[slug]/page.js
export default function BlogPost({ params }) {
    return (
        <div>
            <h1>Blog Post: {params.slug}</h1>
            <p>This is the content for {params.slug}</p>
        </div>
    );
}

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

Multiple Dynamic Segments

// app/products/[category]/[id]/page.js
export default function ProductPage({ params }) {
    const { category, id } = params;
    
    return (
        <div>
            <h1>Product Details</h1>
            <p>Category: {category}</p>
            <p>Product ID: {id}</p>
        </div>
    );
}

// Generate static params for multiple segments
export async function generateStaticParams() {
    const products = await getProducts();
    
    return products.map((product) => ({
        category: product.category,
        id: product.id,
    }));
}

Catch-All Routes

// app/docs/[...slug]/page.js
export default function DocsPage({ params }) {
    const { slug } = params;
    const path = slug.join('/'); // ['getting-started', 'installation'] -> 'getting-started/installation'
    
    return (
        <div>
            <h1>Documentation</h1>
            <p>Current path: {path}</p>
        </div>
    );
}

// Optional catch-all routes
// app/blog/[[...slug]]/page.js - matches /blog, /blog/2023, /blog/2023/12, etc.

Route Groups

// app/(marketing)/about/page.js
// app/(marketing)/contact/page.js
// app/(shop)/products/page.js
// app/(shop)/cart/page.js

// Route groups don't affect the URL structure
// URLs: /about, /contact, /products, /cart

API Routes

Basic API Route

// app/api/hello/route.js
export async function GET() {
    return Response.json({ message: 'Hello World' });
}

export async function POST(request) {
    const data = await request.json();
    return Response.json({ received: data });
}

Dynamic API Routes

// app/api/users/[id]/route.js
export async function GET(request, { params }) {
    const { id } = params;
    const user = await getUser(id);
    
    if (!user) {
        return Response.json({ error: 'User not found' }, { status: 404 });
    }
    
    return Response.json(user);
}

export async function PUT(request, { params }) {
    const { id } = params;
    const data = await request.json();
    
    const updatedUser = await updateUser(id, data);
    return Response.json(updatedUser);
}

export async function DELETE(request, { params }) {
    const { id } = params;
    await deleteUser(id);
    
    return Response.json({ message: 'User deleted' });
}

API Route with Query Parameters

// app/api/search/route.js
export async function GET(request) {
    const { searchParams } = new URL(request.url);
    const query = searchParams.get('q');
    const page = searchParams.get('page') || '1';
    
    const results = await search(query, parseInt(page));
    
    return Response.json({
        results,
        page: parseInt(page),
        query
    });
}

// Usage: /api/search?q=nextjs&page=2

API Route with Middleware

// app/api/protected/route.js
import { NextResponse } from 'next/server';

export async function GET(request) {
    const token = request.headers.get('authorization');
    
    if (!token) {
        return NextResponse.json(
            { error: 'Unauthorized' },
            { status: 401 }
        );
    }
    
    // Verify token and get user data
    const user = await verifyToken(token);
    
    return NextResponse.json({ user });
}

Link Component

import Link from 'next/link';

export default function Navigation() {
    return (
        <nav>
            <Link href="/" className="nav-link">
                Home
            </Link>
            <Link href="/about" className="nav-link">
                About
            </Link>
            <Link href="/blog" className="nav-link">
                Blog
            </Link>
            <Link href="/products" className="nav-link">
                Products
            </Link>
        </nav>
    );
}

Dynamic Links

import Link from 'next/link';

export default function BlogList({ posts }) {
    return (
        <div>
            {posts.map(post => (
                <Link 
                    key={post.id} 
                    href={`/blog/${post.slug}`}
                    className="blog-link"
                >
                    <h2>{post.title}</h2>
                    <p>{post.excerpt}</p>
                </Link>
            ))}
        </div>
    );
}

Programmatic Navigation

'use client';

import { useRouter } from 'next/navigation';

export default function LoginForm() {
    const router = useRouter();
    
    const handleSubmit = async (e) => {
        e.preventDefault();
        
        const formData = new FormData(e.target);
        const response = await fetch('/api/login', {
            method: 'POST',
            body: formData,
        });
        
        if (response.ok) {
            router.push('/dashboard');
        } else {
            router.push('/login?error=invalid');
        }
    };
    
    return (
        <form onSubmit={handleSubmit}>
            <input name="email" type="email" required />
            <input name="password" type="password" required />
            <button type="submit">Login</button>
        </form>
    );
}

Navigation with State

'use client';

import { useRouter, useSearchParams } from 'next/navigation';

export default function SearchComponent() {
    const router = useRouter();
    const searchParams = useSearchParams();
    
    const handleSearch = (query) => {
        const params = new URLSearchParams(searchParams);
        params.set('q', query);
        router.push(`/search?${params.toString()}`);
    };
    
    return (
        <div>
            <input 
                type="text" 
                placeholder="Search..."
                onChange={(e) => handleSearch(e.target.value)}
            />
        </div>
    );
}

Middleware

Basic Middleware

// middleware.js
import { NextResponse } from 'next/server';

export function middleware(request) {
    // Get the pathname
    const path = request.nextUrl.pathname;
    
    // Check if user is authenticated
    const isAuthenticated = request.cookies.get('auth-token');
    
    // Protect dashboard routes
    if (path.startsWith('/dashboard') && !isAuthenticated) {
        return NextResponse.redirect(new URL('/login', request.url));
    }
    
    // Add custom headers
    const response = NextResponse.next();
    response.headers.set('x-custom-header', 'value');
    
    return response;
}

export const config = {
    matcher: [
        '/dashboard/:path*',
        '/api/protected/:path*',
    ],
};

Authentication Middleware

// middleware.js
import { NextResponse } from 'next/server';
import { verifyToken } from './lib/auth';

export async function middleware(request) {
    const token = request.cookies.get('auth-token')?.value;
    
    if (!token) {
        return NextResponse.redirect(new URL('/login', request.url));
    }
    
    try {
        const user = await verifyToken(token);
        
        // Add user info to headers
        const requestHeaders = new Headers(request.headers);
        requestHeaders.set('x-user-id', user.id);
        requestHeaders.set('x-user-role', user.role);
        
        return NextResponse.next({
            request: {
                headers: requestHeaders,
            },
        });
    } catch (error) {
        return NextResponse.redirect(new URL('/login', request.url));
    }
}

export const config = {
    matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};

Internationalization Middleware

// middleware.js
import { NextResponse } from 'next/server';

const locales = ['en', 'es', 'fr'];
const defaultLocale = 'en';

export function middleware(request) {
    const pathname = request.nextUrl.pathname;
    
    // Check if the pathname has a locale
    const pathnameHasLocale = locales.some(
        (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
    );
    
    if (pathnameHasLocale) return;
    
    // Redirect to default locale
    const locale = defaultLocale;
    request.nextUrl.pathname = `/${locale}${pathname}`;
    return NextResponse.redirect(request.nextUrl);
}

export const config = {
    matcher: [
        '/((?!api|_next/static|_next/image|favicon.ico).*)',
    ],
};

Layouts and Nested Routes

Root Layout

// app/layout.js
export default function RootLayout({ children }) {
    return (
        <html lang="en">
            <body>
                <header>
                    <nav>{/* Navigation */}</nav>
                </header>
                
                <main>{children}</main>
                
                <footer>
                    <p>&copy; 2024 My App</p>
                </footer>
            </body>
        </html>
    );
}

Nested Layouts

// app/dashboard/layout.js
export default function DashboardLayout({ children }) {
    return (
        <div className="dashboard-layout">
            <aside className="sidebar">
                <nav>
                    <a href="/dashboard">Overview</a>
                    <a href="/dashboard/profile">Profile</a>
                    <a href="/dashboard/settings">Settings</a>
                </nav>
            </aside>
            
            <main className="content">
                {children}
            </main>
        </div>
    );
}

// app/dashboard/page.js
export default function DashboardPage() {
    return (
        <div>
            <h1>Dashboard</h1>
            <p>Welcome to your dashboard</p>
        </div>
    );
}

Parallel Routes

// app/@modal/(.)login/page.js
export default function LoginModal() {
    return (
        <div className="modal">
            <h2>Login</h2>
            {/* Login form */}
        </div>
    );
}

// app/@modal/(.)register/page.js
export default function RegisterModal() {
    return (
        <div className="modal">
            <h2>Register</h2>
            {/* Register form */}
        </div>
    );
}

// app/layout.js
export default function Layout({ children, modal }) {
    return (
        <html>
            <body>
                {children}
                {modal}
            </body>
        </html>
    );
}

SEO and Metadata

Dynamic Metadata

// app/blog/[slug]/page.js
export async function generateMetadata({ params }) {
    const post = await getPost(params.slug);
    
    return {
        title: post.title,
        description: post.excerpt,
        openGraph: {
            title: post.title,
            description: post.excerpt,
            images: [post.featuredImage],
        },
    };
}

Sitemap Generation

// app/sitemap.js
export default async function sitemap() {
    const baseUrl = 'https://example.com';
    
    // Get all blog posts
    const posts = await getAllPosts();
    const blogUrls = posts.map((post) => ({
        url: `${baseUrl}/blog/${post.slug}`,
        lastModified: post.updatedAt,
        changeFrequency: 'weekly',
        priority: 0.7,
    }));
    
    // Static pages
    const staticPages = [
        {
            url: baseUrl,
            lastModified: new Date(),
            changeFrequency: 'daily',
            priority: 1,
        },
        {
            url: `${baseUrl}/about`,
            lastModified: new Date(),
            changeFrequency: 'monthly',
            priority: 0.8,
        },
    ];
    
    return [...staticPages, ...blogUrls];
}

Robots.txt

// app/robots.js
export default function robots() {
    return {
        rules: [
            {
                userAgent: '*',
                allow: '/',
                disallow: ['/private/', '/admin/', '/api/'],
            },
        ],
        sitemap: 'https://example.com/sitemap.xml',
    };
}

Error Handling

Error Pages

// app/error.js
'use client';

export default function Error({ error, reset }) {
    return (
        <div>
            <h2>Something went wrong!</h2>
            <p>{error.message}</p>
            <button onClick={reset}>Try again</button>
        </div>
    );
}

// app/not-found.js
export default function NotFound() {
    return (
        <div>
            <h2>Page Not Found</h2>
            <p>Could not find the requested resource.</p>
        </div>
    );
}

// app/global-error.js
export default function GlobalError({ error, reset }) {
    return (
        <html>
            <body>
                <h2>Something went wrong!</h2>
                <button onClick={reset}>Try again</button>
            </body>
        </html>
    );
}

Loading States

// app/loading.js
export default function Loading() {
    return (
        <div className="loading">
            <div className="spinner"></div>
            <p>Loading...</p>
        </div>
    );
}

// app/dashboard/loading.js
export default function DashboardLoading() {
    return (
        <div className="dashboard-loading">
            <div className="skeleton-header"></div>
            <div className="skeleton-content"></div>
        </div>
    );
}

Best Practices

Route Organization

// ✅ Good structure
app/
├── (auth)/
│   ├── login/
│   └── register/
├── (dashboard)/
│   ├── dashboard/
│   └── profile/
├── (marketing)/
│   ├── about/
│   └── contact/
└── api/
    ├── auth/
    └── users/

// ❌ Avoid deep nesting
app/
├── very/
│   └── deep/
│       └── nested/
│           └── routes/
│               └── that/
│                   └── are/
│                       └── hard/
│                           └── to/
│                               └── maintain/

Performance Optimization

// Prefetch important routes
import Link from 'next/link';

export default function Navigation() {
    return (
        <nav>
            <Link href="/dashboard" prefetch={true}>
                Dashboard
            </Link>
            <Link href="/profile" prefetch={false}>
                Profile
            </Link>
        </nav>
    );
}

// Use dynamic imports for heavy pages
import dynamic from 'next/dynamic';

const HeavyPage = dynamic(() => import('./HeavyPage'), {
    loading: () => <div>Loading...</div>,
    ssr: false,
});

Security Considerations

// Validate route parameters
export default function UserPage({ params }) {
    const { id } = params;
    
    // Validate ID format
    if (!/^\d+$/.test(id)) {
        notFound();
    }
    
    return <div>User {id}</div>;
}

// Rate limiting in API routes
import { rateLimit } from '@/lib/rate-limit';

export async function POST(request) {
    const limiter = await rateLimit(request);
    
    if (!limiter.success) {
        return Response.json(
            { error: 'Too many requests' },
            { status: 429 }
        );
    }
    
    // Process request
    return Response.json({ success: true });
}