Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
menu
  1. Home
  2. /Web Development
  3. /Nextjs
  4. /Dynamic Routes
Your Progress0%
0 of 70 completed

NextJS Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication

Dynamic Routes and Route Parameters

Creating flexible routes that work for any ID, slug, or identifier

Most real-world applications need routes that work for any identifier—blog posts, user profiles, product pages, and more. You can't create a separate folder for every possible ID! That's where dynamic routes come in. With just square brackets around a folder name, Next.js creates routes that match any value, giving you incredible flexibility with minimal code.

Why We Need Dynamic Routes

Imagine building a blog with 1,000 posts. Without dynamic routes, you'd need:

PLAINTEXT
app/
  blog/
    my-first-post/
      page.tsx
    hello-world/
      page.tsx
    nextjs-tutorial/
      page.tsx
    ... 997 more folders!

This is impossible to maintain! Instead, with dynamic routes, you create one folder that handles all blog posts:

PLAINTEXT
app/
  blog/
    [slug]/           ← One folder handles all posts!
      page.tsx

Now this single route handles:

  • /blog/my-first-post
  • /blog/hello-world
  • /blog/nextjs-tutorial
  • ...and any other slug you can imagine!

The Power of Square Brackets

Wrapping a folder name in square brackets [name] tells Next.js: "This segment can match any value." Whatever appears in the URL at this position becomes available to your component via the params prop.

Creating Your First Dynamic Route

Let's create a blog with dynamic post routes step by step:

Step 1: Create the Folder Structure

PLAINTEXT
app/
  blog/
    page.tsx          ← Blog listing
    [slug]/           ← Dynamic route folder
      page.tsx        ← Individual post page

Step 2: Create the Blog Listing Page

app/blog/page.tsx
import Link from 'next/link';

const posts = [
  { slug: 'first-post', title: 'My First Post' },
  { slug: 'hello-world', title: 'Hello World' },
  { slug: 'learning-nextjs', title: 'Learning Next.js' },
];

export default function BlogPage() {
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-4xl font-bold mb-8">Blog Posts</h1>
      
      <div className="space-y-4">
        {posts.map((post) => (
          <Link
            key={post.slug}
            href={`/blog/${post.slug}`}
            className="block p-4 border rounded-lg hover:shadow-lg transition"
          >
            <h2 className="text-xl font-semibold">{post.title}</h2>
            <p className="text-blue-600">Read more →</p>
          </Link>
        ))}
      </div>
    </div>
  );
}

Step 3: Create the Dynamic Post Page

app/blog/[slug]/page.tsx
// The params prop contains the dynamic segment
interface PageProps {
  params: {
    slug: string;  // This matches the folder name [slug]
  };
}

export default function BlogPostPage({ params }: PageProps) {
  // params.slug contains whatever is in the URL
  // /blog/first-post → params.slug = "first-post"
  // /blog/hello-world → params.slug = "hello-world"
  
  return (
    <div className="container mx-auto px-4 py-8">
      <h1 className="text-4xl font-bold mb-4">
        Blog Post: {params.slug}
      </h1>
      <p className="text-gray-600">
        This is the content for {params.slug}
      </p>
    </div>
  );
}

Step 4: Test It Out

Now visit these URLs in your browser:

  • http://localhost:3000/blog - See the listing
  • http://localhost:3000/blog/first-post - See the post page
  • http://localhost:3000/blog/anything-you-want - Still works!

✨ It Just Works!

Notice how /blog/anything-you-want works even though we never explicitly created that route. That's the magic of dynamic routes!

How Dynamic Routes Work

Dynamic Route Examples

See how [brackets] create flexible route segments

📁 File Structure

app/
  blog/
    [slug]/
      page.tsx

🌐 URL Path

/blog/hello-world
Dynamic Route

[slug] matches 'hello-world'. Access via params.slug

When a user visits a URL:

  1. Next.js looks for matching routes in the app directory
  2. If it finds a dynamic segment [name], it captures that part of the URL
  3. The captured value is passed to your page component as params.name
  4. Your component renders using that value
PLAINTEXT
User visits: /blog/hello-world

Next.js matches: app/blog/[slug]/page.tsx

Extracts: slug = "hello-world"

Passes to component: params = { slug: "hello-world" }

Component receives: { params: { slug: "hello-world" } }

Fetching Data with Dynamic Routes

In real applications, you'll use the dynamic parameter to fetch data from an API or database:

app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';

interface Post {
  slug: string;
  title: string;
  content: string;
  author: string;
  date: string;
}

interface PageProps {
  params: {
    slug: string;
  };
}

export default async function BlogPostPage({ params }: PageProps) {
  // Fetch post data using the slug from the URL
  const res = await fetch(
    `https://api.example.com/posts/${params.slug}`
  );
  
  // Handle not found
  if (!res.ok) {
    notFound(); // Shows 404 page
  }
  
  const post: Post = await res.json();
  
  return (
    <article className="container mx-auto px-4 py-8 max-w-3xl">
      <h1 className="text-4xl font-bold mb-4">{post.title}</h1>
      
      <div className="flex items-center gap-4 text-gray-600 mb-8">
        <span>By {post.author}</span>
        <span>•</span>
        <time>{new Date(post.date).toLocaleDateString()}</time>
      </div>
      
      <div className="prose max-w-none">
        {post.content}
      </div>
    </article>
  );
}

The notFound() Function

Import notFound from next/navigation and call it when a resource doesn't exist. This will display your not-found.tsx page or Next.js's default 404 page.

Multiple Dynamic Segments

You can have multiple dynamic segments in a single route:

Example: User Posts

PLAINTEXT
app/
  users/
    [userId]/
      page.tsx              → /users/123
      posts/
        [postId]/
          page.tsx          → /users/123/posts/456
app/users/[userId]/posts/[postId]/page.tsx
interface PageProps {
  params: {
    userId: string;    // First dynamic segment
    postId: string;    // Second dynamic segment
  };
}

export default async function UserPostPage({ params }: PageProps) {
  // Both params are available!
  const user = await fetch(`/api/users/${params.userId}`)
    .then(r => r.json());
  
  const post = await fetch(`/api/users/${params.userId}/posts/${params.postId}`)
    .then(r => r.json());
  
  return (
    <div>
      <h1>{post.title}</h1>
      <p>By {user.name}</p>
      <div>{post.content}</div>
    </div>
  );
}

Example: E-commerce Categories

PLAINTEXT
app/
  shop/
    [category]/
      page.tsx                    → /shop/electronics
      [subcategory]/
        page.tsx                  → /shop/electronics/laptops
app/shop/[category]/[subcategory]/page.tsx
interface PageProps {
  params: {
    category: string;
    subcategory: string;
  };
}

export default async function SubcategoryPage({ params }: PageProps) {
  const products = await fetch(
    `/api/products?category=${params.category}&subcategory=${params.subcategory}`
  ).then(r => r.json());
  
  return (
    <div>
      <h1>
        {params.category} → {params.subcategory}
      </h1>
      <div className="grid grid-cols-3 gap-4">
        {products.map(product => (
          <div key={product.id}>{product.name}</div>
        ))}
      </div>
    </div>
  );
}

Dynamic Routes Structure

Explore different dynamic route patterns

appImportant

Select a file or folder to see details

Naming Dynamic Segments

You can name dynamic segments whatever makes sense for your application:

Common Names

  • [id] - Numeric IDs
  • [slug] - URL-friendly strings
  • [username] - User identifiers
  • [productId] - Product IDs
  • [postId] - Post IDs
  • [category] - Category names

Usage Examples

PLAINTEXT
app/
  posts/[id]/page.tsx
  → params.id

app/
  blog/[slug]/page.tsx
  → params.slug

app/
  users/[username]/page.tsx
  → params.username

📝 Naming Best Practices

  • Use descriptive names that indicate what the segment represents
  • Be consistent across your app (always use 'id' or always use 'slug')
  • Use camelCase for multi-word names: [userId], [postId]
  • Match your database field names when possible

Generating Static Paths (Optional)

For dynamic routes that you want to pre-render at build time, use generateStaticParams:

app/blog/[slug]/page.tsx
// Tell Next.js which dynamic routes to pre-generate
export async function generateStaticParams() {
  const posts = await fetch('https://api.example.com/posts')
    .then(res => res.json());
  
  // Return array of params objects
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

// This page will be pre-rendered for all returned slugs
export default async function BlogPostPage({
  params,
}: {
  params: { slug: string };
}) {
  const post = await fetch(`https://api.example.com/posts/${params.slug}`)
    .then(res => res.json());
  
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

This tells Next.js:

  • "Pre-render these specific slugs at build time" (Static Site Generation)
  • Other slugs can still be accessed (rendered on-demand)
  • Improves performance for known, popular pages

When to Use generateStaticParams

  • Blog posts that change rarely
  • Product pages for your catalog
  • Documentation pages
  • Any content you want maximum performance for

Dynamic Metadata for SEO

Generate metadata based on the dynamic route parameter:

app/products/[id]/page.tsx
import { Metadata } from 'next';

interface PageProps {
  params: { id: string };
}

// Generate metadata dynamically
export async function generateMetadata({
  params,
}: PageProps): Promise<Metadata> {
  const product = await fetch(`https://api.example.com/products/${params.id}`)
    .then(res => res.json());
  
  return {
    title: `${product.name} - Our Store`,
    description: product.description,
    openGraph: {
      title: product.name,
      description: product.description,
      images: [product.image],
    },
  };
}

export default async function ProductPage({ params }: PageProps) {
  const product = await fetch(`https://api.example.com/products/${params.id}`)
    .then(res => res.json());
  
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <p>${product.price}</p>
    </div>
  );
}

This ensures each product page has unique, SEO-optimized metadata based on the actual product data!

Common Dynamic Route Patterns

1. Blog/Article Platform

PLAINTEXT
app/
  blog/
    page.tsx                    → /blog (listing)
    [slug]/
      page.tsx                  → /blog/my-post
    category/
      [categorySlug]/
        page.tsx                → /blog/category/tutorials

2. E-commerce Site

PLAINTEXT
app/
  products/
    page.tsx                    → /products (all products)
    [id]/
      page.tsx                  → /products/123
      reviews/
        page.tsx                → /products/123/reviews
  categories/
    [category]/
      page.tsx                  → /categories/electronics

3. Social Media Profile

PLAINTEXT
app/
  [username]/
    page.tsx                    → /john (profile)
    posts/
      page.tsx                  → /john/posts
      [postId]/
        page.tsx                → /john/posts/456
    followers/
      page.tsx                  → /john/followers

4. Documentation Site

PLAINTEXT
app/
  docs/
    page.tsx                    → /docs (intro)
    [category]/
      page.tsx                  → /docs/getting-started
      [page]/
        page.tsx                → /docs/getting-started/installation

Linking to Dynamic Routes

Use the Next.js Link component with template literals:

TYPESCRIPT
import Link from 'next/link';

function BlogList({ posts }) {
  return (
    <div>
      {posts.map((post) => (
        <Link 
          key={post.id}
          href={`/blog/${post.slug}`}
        >
          {post.title}
        </Link>
      ))}
    </div>
  );
}

// With multiple dynamic segments
function UserPostsList({ userId, posts }) {
  return (
    <div>
      {posts.map((post) => (
        <Link
          key={post.id}
          href={`/users/${userId}/posts/${post.id}`}
        >
          {post.title}
        </Link>
      ))}
    </div>
  );
}

Programmatic Navigation

TYPESCRIPT
"use client";

import { useRouter } from 'next/navigation';

export default function ProductCard({ productId }) {
  const router = useRouter();
  
  const handleClick = () => {
    // Navigate to dynamic route programmatically
    router.push(`/products/${productId}`);
  };
  
  return (
    <button onClick={handleClick}>
      View Product
    </button>
  );
}

Practice: Build a Product Catalog

Let's build a complete product catalog with dynamic routes:

Product Detail Page with Dynamic Routes

Try changing the product ID in the URL (1, 2, or 3)

page.tsx

Output Preview

Click "Run Code" to see the output

🎯 Try This Exercise

Create these dynamic routes in your project:

  1. A team member profile page: app/team/[memberId]/page.tsx
  2. A project showcase: app/projects/[projectSlug]/page.tsx
  3. Nested services: app/services/[category]/[serviceId]/page.tsx

Dynamic Routes Best Practices

1. Validate Route Parameters

TYPESCRIPT
export default async function ProductPage({ 
  params 
}: { params: { id: string } }) {
  // Validate the parameter
  const id = parseInt(params.id);
  
  if (isNaN(id) || id < 1) {
    notFound();
  }
  
  // Continue with valid ID...
}

2. Handle Not Found Cases

TYPESCRIPT
import { notFound } from 'next/navigation';

export default async function Page({ params }) {
  const data = await fetch(`/api/items/${params.id}`)
    .then(res => res.ok ? res.json() : null);
  
  if (!data) {
    notFound(); // Shows 404
  }
  
  return <div>{/* Render data */}</div>;
}

3. Use TypeScript for Type Safety

TYPESCRIPT
// Define your params interface
interface PageProps {
  params: {
    id: string;
    // Add all your dynamic segments
  };
  searchParams?: {
    [key: string]: string | string[] | undefined;
  };
}

export default async function Page({ 
  params, 
  searchParams 
}: PageProps) {
  // TypeScript will catch errors!
}

4. Sanitize User Input

TYPESCRIPT
export default async function Page({ params }) {
  // Sanitize the parameter before using in queries
  const safeSlug = params.slug
    .toLowerCase()
    .replace(/[^a-z0-9-]/g, '');
  
  // Use sanitized value
  const data = await fetchPost(safeSlug);
}

Common Issues and Solutions

Issue 1: params is undefined

Problem: Getting "Cannot read property 'slug' of undefined"

Solutions:

  • Make sure folder name matches: [slug] not [Slug] or [SLUG]
  • Verify you're accessing the correct property name
  • Check TypeScript interface matches folder name

Issue 2: Route returns 404

Problem: Dynamic route shows 404

Solutions:

  • Ensure the folder has page.tsx inside
  • Check folder name has square brackets: [id] not (id) or {id}
  • Restart dev server after creating new dynamic routes

Issue 3: Getting wrong parameter value

Problem: params.id shows undefined but params.slug works

Solution: The property name in params matches the folder name. If your folder is [slug], use params.slug, not params.id

Key Takeaways

  • Square brackets create dynamic routes - [id], [slug], [name]
  • Dynamic segments match any single value at that position in the URL
  • Access via params prop - automatically passed to page components
  • Property name matches folder name - [slug] → params.slug
  • Can have multiple dynamic segments - nested or at same level
  • Use generateStaticParams - for pre-rendering at build time
  • Always validate parameters - check if data exists, use notFound()
  • TypeScript recommended - catch errors early with proper typing

What's Next?

You've mastered basic dynamic routes with single segments like [id]! But what if you need routes that match multiple segments or optional segments? That's where catch-all routes come in.

In the next lesson, we'll explore [...slug] and [[...slug]] patterns that give you even more routing flexibility—perfect for documentation sites, file browsers, and complex hierarchies!

🚀 Practice Makes Perfect

Dynamic routes are fundamental to most applications. Practice by building different types of pages: blog posts, user profiles, product details, and more. The patterns you learn here will serve you throughout your Next.js journey!

Test Your Understanding

Question 1 of 4

How do you create a dynamic route segment in Next.js?

Master dynamic routes in Next.js! Learn how [slug] creates flexible routes for any ID or identifier.

Previous
Creating Pages with page.tsx
Next
Catch-All and Optional Catch-All Routes

Master Next.js Routing

Join 2,000+ developers building with Next.js. Get the next lesson on catch-all routes delivered to your inbox - 100% FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

NextJS Tutorials

0 of 70 completed

Your Progress0%

Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo