Next.js has revolutionized React development with its powerful routing system. Among its most flexible features are dynamic routes, which allow developers to create pages that can match multiple URL patterns. Two particularly powerful patterns are
Next.js has revolutionized React development with its powerful routing system. Among its most flexible features are dynamic routes, which allow developers to create pages that can match multiple URL patterns. Two particularly powerful patterns are the catch-all route ([...path]
) and the optional catch-all route ([[...path]]
). While they might look nearly identical with just an extra pair of brackets, their behavior differs significantly, impacting how your application handles URLs and user navigation.
This guide will break down each pattern in detail, providing clear examples and use cases to help both beginners and experienced Next.js developers make informed decisions about which pattern to use in their projects.
Before diving into the specific patterns, let's establish a foundation for understanding Next.js routing.
Next.js uses a file-system based router where:
Files in the pages
directory (or app
directory in the newer App Router) automatically become routes
Files named with square brackets like [id].js
create dynamic routes that can match different values
Dynamic routes are essential for content-driven websites and applications where the exact number and structure of pages isn't known in advance.
The catch-all route, represented by [...path]
, allows a single page component to handle multiple URL segments at once.
When you create a file named [...path].js
in your pages directory, it will match any route with one or more segments where the parameter appears.
If you have a file at pages/blog/[...slug].js
:
β
/blog/2023 β matches
β
/blog/nextjs/routes β matches
β
/blog/2023/01/post β matches
β /blog β does NOT match
The matched segments are available as an array via the params
object:
// For URL: /blog/2023/nextjs/routes
// In pages/blog/[...slug].js:
export function getServerSideProps({ params }) {
console.log(params.slug) // ['2023', 'nextjs', 'routes']
// ...
}
Required segments: There must be at least one segment in the path for it to match. The base path alone (/blog
in our example) will not match.
The optional catch-all route, represented by [[...path]]
, extends the functionality of the regular catch-all route by making the parameters entirely optional.
When you create a file named [[...path]].js
, it will match the same routes as a catch-all route, but will also match the base path with no additional segments.
If you have a file at pages/blog/[[...slug]].js
:
β
/blog β matches
β
/blog/2023 β matches
β
/blog/nextjs/routes β matches
β
/blog/2023/01/post β matches
Similar to the catch-all route, but with a special case for the base path:
javascript
// For URL: /blog
// In pages/blog/[[...slug]].js:
export function getServerSideProps({ params }) {
console.log(params.slug) // undefined or []
// ...
}
// For URL: /blog/2023/nextjs
// In pages/blog/[[...slug]].js:
export function getServerSideProps({ params }) {
console.log(params.slug) // ['2023', 'nextjs']
// ...
}
Optional segments: The parameter can match zero or more segments, making it entirely optional.
Documentation sites: When you need to organize content in a hierarchical structure
/docs/getting-started/installation
/docs/advanced/optimization/images
Blog posts with categories: When posts are organized by date, category, or other hierarchical metadata
/blog/technology/javascript/es6-features
Product categories: For e-commerce sites with nested product categories
/products/electronics/computers/laptops
Content sections with landing pages: When you need both a section landing page and nested content
/learn (Overview page)
/learn/basics (Specific content)
/learn/advanced/topics (Deeper nested content)
Search functionality: Where you might have optional filters in the URL
/search (Base search)
/search/products/shoes (Filtered search)
User profiles with tabs: Where you have a main profile page and optional sections
/profile (Main profile)
/profile/posts (Posts tab)
/profile/friends/list (Friends list tab)
Always validate the parameters to ensure they make sense for your application logic:
javascript
export async function getServerSideProps({ params }) {
// For [[...slug]], check if params.slug exists
const slugs = params.slug || [];
// Validate the path segments
if (slugs.length > 3) {
return {
notFound: true // Return 404 page for invalid paths
};
}
// Continue with valid paths
return {
props: {
// ...
}
};
}
Next.js has a predefined route matching priority:
Static routes (/about
)
Dynamic routes (/posts/[id]
)
Catch-all routes (/posts/[...slug]
)
Optional catch-all routes (/posts/[[...slug]]
)
This means more specific routes will take precedence over catch-all routes. Use this to your advantage when designing your routing structure.
Next.js middleware can be combined with catch-all routes for even more control:
javascript
// middleware.js
export function middleware(request) {
const url = request.nextUrl.clone();
const { pathname } = url;
// Handle specific paths within a catch-all pattern
if (pathname.startsWith('/dashboard/')) {
// Check authentication, redirect, etc.
}
}
export const config = {
matcher: ['/dashboard/:path*'],
};
When fetching data based on dynamic segments, remember to handle all possible cases:
javascript
async function fetchData(slugs = []) {
if (slugs.length === 0) {
return fetchOverviewData();
} else if (slugs.length === 1) {
return fetchCategoryData(slugs[0]);
} else {
return fetchNestedData(slugs);
}
}
For TypeScript users, properly type your route parameters:
typescript
// For [...slug].js
type Params = {
slug: string[];
};
// For [[...slug]].js
type Params = {
slug?: string[];
};
export const getServerSideProps: GetServerSideProps<Props, Params> =
async ({ params }) => {
const slugs = params?.slug || [];
// ...
};
When using Next.js's Link
component with dynamic routes, ensure you're passing the correct href structure:
// For nested paths
<Link href="/blog/[...slug]" as={`/blog/${category}/${year}/${post}`}>
Read post
</Link>
// For optional catch-all routes
<Link href="/profile/[[...tab]]" as={tab ? `/profile/${tab}` : "/profile"}>
View profile
</Link>
If you need to migrate from a catch-all route to an optional catch-all route (or vice versa), consider these steps:
Create the new route file with the updated pattern
Update your component to handle the difference in parameter behavior
Add redirects for any changed URL patterns in your next.config.js
:
module.exports = {
async redirects() {
return [
{
source: '/blog',
destination: '/blog/all',
permanent: true,
},
];
},
};
When using catch-all routes with getStaticProps
, you'll need to define which paths to pre-render using getStaticPaths
:
javascript
export async function getStaticPaths() {
// For [...slug] (regular catch-all)
return {
paths: [
{ params: { slug: ['2023'] } },
{ params: { slug: ['2023', 'nextjs'] } },
],
fallback: 'blocking'
};
// For [[...slug]] (optional catch-all)
// Don't forget to include the base path
return {
paths: [
{ params: { slug: [] } }, // Important for [[...slug]]
{ params: { slug: ['2023'] } },
{ params: { slug: ['2023', 'nextjs'] } },
],
fallback: 'blocking'
};
}
For data-driven sites, combine catch-all routes with Incremental Static Regeneration for optimal performance:
export async function getStaticProps({ params }) {
const slugs = params.slug || [];
const data = await fetchData(slugs);
return {
props: {
data,
},
revalidate: 60, // Regenerate page after 60 seconds
};
}
Understanding the difference between [...path]
and [[...path]]
in Next.js is crucial for building flexible, intuitive routing structures in your applications. The standard catch-all route ([...path]
) is perfect for hierarchical content where you need at least one parameter, while the optional catch-all route ([[...path]]
) provides additional flexibility by making the parameter entirely optional.
By choosing the right pattern for your specific use case, you can create more intuitive URLs, simplify your code, and improve the overall user experience of your Next.js application. Remember that routing is a fundamental aspect of web application usability, and thoughtful implementation will pay dividends in maintainability and user satisfaction.
Vercel Platform - The creators of Next.js
If you found this tutorial helpful, feel free to share it with others. And donβt forget to follow me on Twitter/X for more web development tips and tutorials.
Happy coding! π
---
Connect with Me
- GitHub: https://github.com/lucky-victory
- Twitter: @CodewithVick
Get the latest posts delivered right to your inbox!