Nextjs Nested Routes And Optional Catch All Routes Complete Guide

 Last Update:2025-06-22T00:00:00     .NET School AI Teacher - SELECT ANY TEXT TO EXPLANATION.    11 mins read      Difficulty-Level: beginner

Understanding the Core Concepts of Nextjs Nested Routes and Optional Catch all Routes

Understanding Nested Routes in Next.js

Nested Routes Overview: In Next.js, nested routes help organize complex applications by allowing you to define child pages within directories that correspond to the URL structure. This mimics the hierarchical nature seen in traditional websites while leveraging the simplicity and efficiency of modern client-side routing.

Creating Nested Routes: To create nested routes, follow a straightforward folder structure. If you want your URL paths like /users/[userId]/orders, you simply need to create the corresponding folder structure within your pages directory:

pages/
  users/
    [userId]/
      orders.js
  • Parent Page (Optional): You can also include a parent page, such as pages/users/[userId].js, which will be loaded when accessing any path under /users/[userId]. This is useful for layout components or shared functionality.

Example Use Case: Imagine a user management system where each user's profile has a section that displays their orders. Nested routes would allow you to organize these pages into a logical hierarchy as shown above, making it easier to manage the code and enhance maintainability.

Accessing Routing Props: When working with dynamic segments (like [userId]), Next.js automatically supplies the relevant parameters through the routing prop router.query. For instance, in /users/[userId]/orders.js, you can access userId via router.query.userId.

// Inside a child component
import { useRouter } from 'next/router';

function OrdersPage() {
  const router = useRouter();
  const userId = router.query.userId;
  return <div>Orders for User ID: {userId}</div>;
}

Optional Catch-all Routes in Next.js

Optional Catch-all Overview: Next.js offers optional catch-all routes, which allow you to capture an indefinite number of URL segments dynamically, optionally. These are useful in scenarios where you want to handle a wider variety of URL structures without explicitly defining each possible route.

Defining Optional Catch-all Routes: To define an optional catch-all route, create a [...slug].js file in your desired directory. The brackets (and ellipsis inside) denote the dynamic segment that can capture multiple path segments.

For example, /posts/[[...slug]].js allows the following:

  • /posts/a
  • /posts/a/b
  • /posts/a/b/c
  • Optionally, it can also match just /posts.

Usage Example: Suppose you manage blog posts and some posts might have tags or additional categorization. Optional catch-all routes enable flexible handling of URLs without predefined structures for every possibility.

// pages/posts/[[...slug]].js

export async function getStaticPaths() {
  // Define possible slugs here, but since they're optional,
  // we might also return an empty array if no specific routes
  // are known at build time.
  return {
    paths: [
      '/posts/a',
      '/posts/a/b',
      '/posts/a/b/c',
    ],
    fallback: true, // Can be false or 'blocking'
  };
}

export async function getStaticProps({ params }) {
  // Params contains `slug`: containing `['a', 'b']` or `undefined`
  console.log(params);
  const data = await fetchDataFromAPI(params?.slug);

  return {
    props: {
      data
    }
  }
}

function PostPage({ data }) {
  return (
    <div>
      <h1>Post Title</h1>
      <article>
        {/* Render post data */}
        {data && data.map(post => (
          <section key={post.id}>{post.content}</section>
        ))}
      </article>
    </div>
  );
}

Key Points:

  • Fallback: The fallback property in getStaticPaths() dictates behavior when a dynamic path is not pre-rendered. It can be true, false, or 'blocking'.

    • true: Generates new pages on demand when a user visits the page (SSG first-time loading).
    • false: Returns a 404 error if the path isn't explicitly defined.
    • 'blocking': Waits for the page to be generated on the server before rendering it. This improves SEO as the page becomes instantly available to search engines.
  • Dynamic Segment Retrieval: The optional catch-all dynamic segment captured as params.slug is either undefined (if no additional paths are provided) or an array (containing path segments).

Conclusion

By utilizing nested routes and optional catch-all routes, developers can craft robust URL structures in Next.js applications that mirror real-world navigation patterns. These mechanisms not only simplify the organization of page components but also provide flexibility and scalability to accommodate various URL formats dynamically.

Additional Keywords (Up to 700):

Routing, Next.js, dynamic routes, static optimization, getStaticPaths, getStaticProps, fallback, SSG, dynamic segments, URL hierarchy, nested directories, React components, layout management, maintainable code, blog management, SEO improvements, client-side routing, server-side rendering, URL parameters, page pre-rendering, user profiles, flexible application, route handling, infinite URL possibilities, path generation, first-time loading, optional URL components, search engine optimization.

Online Code run

🔔 Note: Select your programming language to check or run code at

💻 Run Code Compiler

Step-by-Step Guide: How to Implement Nextjs Nested Routes and Optional Catch all Routes

Understanding Nested Routes

Nested routes in Next.js allow you to create a file-based routing structure where you can have parent and child routes. This is particularly useful when you have sections of your application that logically belong under a parent route.

Understanding Optional Catch-All Routes

Optional Catch-All routes capture the rest of the URL path if it matches the pattern, but they are not required to be present. This allows you to handle URLs dynamically and optionally.


Example 1: Basic Nested Routes

Let's create a simple blog application where:

  • /blog shows a list of blog posts.
  • /blog/[slug] shows an individual blog post based on the slug (URL fragment).

Project Setup

First, create a new Next.js project if you haven't already:

npx create-next-app@latest nextjs-blog
cd nextjs-blog

Step 1: Create Parent Route (/blog)

Create a pages/blog/index.js file which renders the blog list.

// pages/blog/index.js
export default function BlogIndex() {
  return (
    <div>
      <h1>Blog Posts</h1>
      <ul>
        <li>
          <a href="/blog/first-post">First Post</a>
        </li>
        <li>
          <a href="/blog/learn-nextjs">Learn Next.js</a>
        </li>
        <li>
          <a href="/blog/deploying-nextjs">Deploying Next.js</a>
        </li>
      </ul>
    </div>
  );
}

Step 2: Create Child Route (/blog/[slug])

Create a [slug].js file inside the pages/blog directory which will handle the rendering of individual blog posts. You can use getStaticPaths and getStaticProps to fetch data for each post. For simplicity, we'll mock the data.

// pages/blog/[slug].js
import { getBlogPostBySlug } from '../../lib/api';

export default function BlogPost({ post }) {
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}

// Get all slugs from your database or mock data
export async function getStaticPaths() {
  // Mocked static paths for demonstration purposes
  const paths = [
    { params: { slug: 'first-post' } },
    { params: { slug: 'learn-nextjs' } },
    { params: { slug: 'deploying-nextjs' } }
  ];

  return {
    paths,
    fallback: false
  };
}

// Fetch the data for the specific post based on the slug
export async function getStaticProps({ params }) {
  const { slug } = params;
  const post = getBlogPostBySlug(slug);

  return {
    props: {
      post
    }
  };
}

// Mocked API function to simulate fetching a blog post by slug
function getBlogPostBySlug(slug) {
  const posts = {
    'first-post': {
      title: 'The First Post',
      content: 'This is the first blog post.'
    },
    'learn-nextjs': {
      title: 'Learning Next.js',
      content: 'Next.js is great for building server-side rendered applications.'
    },
    'deploying-nextjs': {
      title: 'Deploying Next.js Applications',
      content: 'You can deploy Next.js apps using Vercel and other platforms.'
    }
  };

  return posts[slug];
}

Testing Nested Routes

Run your development server:

npm run dev

Navigate to:

  • /blog to see the list of blog posts.
  • /blog/first-post to see the content of the first post.
  • /blog/learn-nextjs and /blog/deploying-nextjs similarly display their respective contents.

Example 2: Optional Catch-All Routes

Let's expand the blog application to include an optional catch-all route to handle blog categories dynamically. For example:

  • /blog/category/[...category] should show all posts within a category.
  • /blog/category/react and /blog/category/react/nextjs should be valid.
  • If category doesn't exist, it should fallback to a more generic page.

Step 1: Create Categories Route

Create a [...category].js file inside the pages/blog/category directory:

// pages/blog/category/[...category].js
import { getPostsByCategory } from '../../lib/api';

export default function BlogCategory({ posts, categoryPath = 'Default Category' }) {
  return (
    <div>
      <h1>Blog Posts for Category: {categoryPath}</h1>
      <ul>
        {posts.map((post) => (
          <li key={post.slug}>
            <a href={`/blog/${post.slug}`}>{post.title}</a>
          </li>
        ))}
      </ul>
    </div>
  );
}

// Fetch all possible categories from your database or mock data
export async function getStaticPaths() {
  // Mocked static paths for demonstration purposes
  const paths = [
    { params: { category: ['react'] } },
    { params: { category: ['react', 'nextjs'] } },
    { params: { category: ['other'] } }
  ];

  return {
    paths,
    fallback: true // Enable dynamic generation if needed
  };
}

// Fetch the data for the specific category based on the slug fragments
export async function getStaticProps({ params }) {
  const { category } = params;
  let categoryPath = category ? category.join(' > ') : 'Default Category';
  let posts = getPostsByCategory(category);

  // Fallback for non-existent categories
  if (!posts || posts.length === 0) {
    posts = [{ title: 'No posts found', slug: '' }];
    categoryPath = 'Default Category';
  }

  return {
    props: {
      posts,
      categoryPath
    }
  };
}

// Mocked API function to simulate fetching posts by category
function getPostsByCategory(categories) {
  const posts = {
    react: [
      {
        slug: 'react-hooks-intro',
        title: 'Introduction to React Hooks'
      },
      {
        slug: 'redux-with-react',
        title: 'Using Redux with React'
      }
    ],
    'react,nextjs': [
      {
        slug: 'using-nextjs-with-react',
        title: 'Combining Next.js with React'
      }
    ],
    other: [
      {
        slug: 'generic-post',
        title: 'Generic Post Title'
      }
    ]
  };

  return categories && categories.join(',') in posts ? posts[categories.join(',')] : [];
}

Step 2: Update getBlogPostBySlug and BlogIndex

To include links to the category routes, update your getBlogPostBySlug function to also store a category, and adjust the BlogIndex component to link to these routes.

// lib/api.js
export function getBlogPostBySlug(slug) {
  const postsWithCategories = {
    'first-post': {
      title: 'The First Post',
      content: 'This is the first blog post.',
      category: ['general']
    },
    'learn-nextjs': {
      title: 'Learning Next.js',
      content: 'Next.js is great for building server-side rendered applications.',
      category: ['react', 'nextjs']
    },
    'deploying-nextjs': {
      title: 'Deploying Next.js Applications',
      content: 'You can deploy Next.js apps using Vercel and other platforms.',
      category: ['development']
    },
    'react-hooks-intro': {
      title: 'Introduction to React Hooks',
      content: 'Hooks allow you to use state and other React features in functional components.',
      category: ['react']
    },
    'redux-with-react': {
      title: 'Using Redux with React',
      content: 'Redux is a predictable state container for JavaScript apps.',
      category: ['react']
    },
    'using-nextjs-with-react': {
      title: 'Combining Next.js with React',
      content: 'Next.js works seamlessly with React and allows server-side rendering.',
      category: ['react', 'nextjs']
    },
    'generic-post': {
      title: 'Generic Post Title',
      content: 'Here is a generic blog post for the `other` category.',
      category: ['other']
    }
  };

  return postsWithCategories[slug];
}

import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import Link from 'next/link';

// pages/blog/index.js
export default function BlogIndex() {
  const router = useRouter();
  const [categories, setCategories] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      const fetchedCategories = [
        { path: '/blog/category/react', name: 'React' },
        { path: '/blog/category/react/nextjs', name: 'React > Next.js' },
        { path: '/blog/category/general', name: 'General' },
        { path: '/blog/category/development', name: 'Development' },
        { path: '/blog/category/other', name: 'Other' }
      ];

      setCategories(fetchedCategories);
      setLoading(false);
    };

    fetchData();
  }, []);

  return (
    <div>
      <h1>Blog Posts</h1>
      <ul>
        <li>
          <Link href="/blog/first-post">First Post</Link>
        </li>
        <li>
          <Link href="/blog/learn-nextjs">Learn Next.js</Link>
        </li>
        <li>
          <Link href="/blog/deploying-nextjs">Deploying Next.js</Link>
        </li>
        <li>
          <Link href="/blog/react-hooks-intro">React Hooks Intro</Link>
        </li>
        <li>
          <Link href="/blog/redux-with-react">Redux with React</Link>
        </li>
        <li>
          <Link href="/blog/using-nextjs-with-react">Using Next.js with React</Link>
        </li>
        <li>
          <Link href="/blog/generic-post">Generic Post</Link>
        </li>
      </ul>

      {!loading && (
        <div>
          <h2>Categories</h2>
          <ul>
            {categories.map((cat) => (
              <li key={cat.path}>
                <Link href={cat.path}>{cat.name}</Link>
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}

Testing Optional Catch-All Routes

Run your development server:

npm run dev

Navigate to:

  • /blog/category/react
  • /blog/category/react/nextjs
  • /blog/category/other

These URLs will display dynamic blog posts based on category. You can experiment by creating URLs that don't exist, like /blog/category/nonexistent, and observe how the application falls back to the Default Category.


Final Thoughts and Conclusion

  • Nested Routes: Use these to organize your application into logical sections with parent-child relationships. They help manage the URL structure effectively.
  • Optional Catch-All Routes: Enable you to build flexible applications that can dynamically handle varying URL patterns. They provide a good way to handle complex navigation needs where certain segments of the URL are optional.

Remember, while this example uses static paths for simplicity, in real-world applications, you'd typically fetch these paths directly from your database or CMS.

Top 10 Interview Questions & Answers on Nextjs Nested Routes and Optional Catch all Routes

1. What are Nested Routes in Next.js?

Answer: Nested routes in Next.js allow you to create a hierarchy of pages within your application. You can organize your page files into subdirectories to reflect this structure. For example, placing pages/dashboard/settings.js creates a nested route /dashboard/settings. This enables developers to build complex applications with a clear file structure that mirrors the URLs.

2. How Do I Create Nested Pages in Next.js?

Answer: To create nested pages in Next.js, simply add subdirectories within the pages directory. Each subdirectory represents a segment of the URL path. Files within these directories become nested routes. For example:

pages/
-- dashboard/
---- index.js          // Route: /dashboard
---- settings.js       // Route: /dashboard/settings
---- analytics/
------ overview.js     // Route: /dashboard/analytics/overview

3. Can Nested Routes Have Dynamic Segments?

Answer: Yes, nested routes can include dynamic segments. These segments are defined by square brackets around the filename and function the same way as regular dynamic routes:

pages/
-- blogs/
---- [slug].js      // Dynamic route: /blogs/some-blog-title
---- categories/
------ [id].js      // Nested dynamic route: /blogs/categories/5

4. What Are Optional Catch All Routes in Next.js?

Answer: Optional catch all routes allow you to capture a variable number of URL segments without being required. Unlike standard catch all routes (which require at least one segment), optional catch all routes can match any path including no additional segments at all. They are denoted by three dots followed by the parameter name inside square brackets ([...param]).

5. How Do I Create an Optional Catch All Route?

Answer: An optional catch all route is created by using the syntax [...param]. Consider this example:

pages/
-- projects/
---- [...slug].js    // Matches /projects, /projects/project-1, /projects/group/project-1

6. Can Optional Catch All Routes Be Nested?

Answer: Yes, optional catch all routes can be nested. This allows you to create deeply structured routing systems where certain segments are optional:

pages/
-- blog/
---- post/[...slug].js      // Route: /blog/post, /blog/post/title, /blog/post/year/month/title

7. How Do I Access Dynamic Segments from Optional Catch All Routes?

Answer: Inside your optional catch all component, you can access the captured segments using useRouter() hook or by accessing props.query if you’re using getStaticProps or getServerSideProps. The segments will be provided as an array:

import { useRouter } from 'next/router';

function BlogPost() {
  const router = useRouter();
  const slug = router.query.slug;  // slug will be an array of segments ["year", "month", "title"]

  return <div>Blog Post: {slug.join('-')}</div>;
}

8. What are Use Cases for Optional Catch All Routes?

Answer: Optional catch all routes are ideal for scenarios where a section of the URL can vary arbitrarily or some sections may not be present at all:

  • Multi-level blog posts like /blog/year/month/post-title.
  • E-commerce category browsing like /products, /products/electronics, /products/electronics/laptops/hp-envy.
  • Flexible content management systems where paths can differ based on user input or hierarchy levels.

9. Can I Mix Optional Catch All Routes with Other Route Types in Next.js?

Answer: Absolutely! Mixing optional catch all routes with static routes, dynamic routes, or other route types is perfectly valid. This provides the flexibility needed to match various URL patterns efficiently:

pages/
-- products/
---- index.js        // Matches /products
---- [...slugs].js   // Matches /products, /products/electronics, /products/electronics/laptops
---- category.js     // Matches /products/category

In this case, requesting /products will hit products/index.js. /products/category will load products/category.js, while any other path will match the optional catch all products/[...slugs].js.

10. How Can I Handle Different Scenarios with Optional Catch All Routes?

Answer: Handling different scenarios with optional catch all routes often involves checking the length and contents of the slug array inside the route component and determining the appropriate response or action:

import { useRouter } from 'next/router';

export default function ProjectPage() {
  const router = useRouter();
  const slugs = router.query.slugs || [];

  let content;
  if (slugs.length === 0) {
    content = <div>Project List</div>;
  } else if (slugs.length === 1) {
    content = <div>Project Category: {slugs[0]}</div>;
  } else if (slugs.length === 2) {
    content = <div>Subcategory: {slugs[0]}, Project Type: {slugs[1]}</div>;
  } else {
    content = <div>Project Detail: {slugs.join('/')}</div>;
  }

  return <>{content}</>;
}

// Server-side rendering
export async function getServerSideProps(context) {
  const slugs = context.query.slugs || [];
  
  // Different data fetching logic based on slugs array
  let data;
  switch (slugs.length) {
    case 0:
      data = await fetchProjectsList();
      break;
    case 1:
      data = await fetchProjectsByCategory(slugs[0]);
      break;
    default:
      data = await fetchDetailedProject(slugs);
  }
  
  return { props: { data } };
}

Here, ProjectPage adjusts its displayed content based on the URL segments captured by the optional catch all route.

You May Like This Related .NET Topic

Login to post a comment.