Next.js offers a powerful way to create applications that can seamlessly handle different URL patterns. Its dynamic routing feature lets you create routes that are adaptable and can be generated based on data. This is perfect for websites that have content like blog posts, e-commerce products, or any structure where individual pages follow a similar template but have unique content.
Understanding Dynamic Routing
In Next.js, dynamic routes are defined by using square brackets []
within your pages directory to represent dynamic segments of a URL. Let's say you have a blog, here are some examples of how this might look:
pages/blog/[postId].js: Handles URLs like
/blog/1
,/blog/20
, etc.pages/products/[category]/[productId].js: Handles URLs like
/products/electronics/laptop-xyz
Handling Dynamic Route Parameters
When a request is made to a dynamic route, Next.js parses the URL and extracts the value of the dynamic segment. This value is then passed as a parameter to the page component or to data-fetching functions like getServerSideProps
or getStaticProps
. In the page component, you can access these parameters using the useRouter
hook or as props, depending on how you've set up your data fetching.
Basic Dynamic Routing
Dynamic routing is perfect for scenarios where you have a large number of similar pages, such as individual blog posts or product pages. For example, in a blog application, you might have a dynamic route for individual posts:
File structure:
pages/
└── posts/
└── [id].tsx
In pages/posts/[id].tsx
, you can fetch the post data based on the id
and display it:
import { useRouter } from 'next/router';
const Post = () => {
const router = useRouter();
const { id } = router.query;
// Fetch the post data based on the id
// const postData = fetchPostData(id);
return <div>Post ID: {id}</div>;
};
export default Post;
Fetching Data and Navigation
When using dynamic routing in Next.js, you often need to fetch data based on the dynamic segment in the URL and navigate to the appropriate URL based on user interactions. Let's consider a more detailed example using a product page in an e-commerce application.
File Structure
First, let's set up the file structure for our dynamic product page:
pages/
└── products/
└── [slug].tsx
In this structure, [slug].tsx
is a dynamic route that will match any path like /products/some-product-slug
.
Product Page Component-CSR
In pages/products/[slug].tsx
, we'll fetch the product data based on the slug
and display it:
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { NextPage } from "next";
interface Product {
id: number;
name: string;
description: string;
slug: string;
price: number;
}
const ProductPage: NextPage = () => {
const router = useRouter();
const { slug } = router.query;
const [product, setProduct] = useState<Product | null>(null);
useEffect(() => {
if (slug) {
fetchProductData(slug as string).then(setProduct);
}
}, [slug]);
if (!product) {
return <div>Loading...</div>;
}
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>Price: ${product.price}</p>
</div>
);
};
async function fetchProductData(slug: string): Promise<Product> {
// Simulate fetching product data from an API or database
const products: Product[] = [
{
id: 1,
name: "Product 1",
description: "Description of Product 1",
slug: "product-1",
price: 100,
},
{
id: 2,
name: "Product 2",
description: "Description of Product 2",
slug: "product-2",
price: 200,
},
];
return products.find((product) => product.slug === slug);
}
export default ProductPage;
In the ProductPage
component, we're using the useRouter
hook to access the slug
parameter from the URL. We then use this slug
to fetch the product data in a useEffect
hook. This approach is useful when you want to fetch data on the client side or when the data needs to be refreshed without a full page reload.
Product Page Component-SSR
import { GetServerSideProps, NextPage } from 'next';
interface Product {
id: number;
name: string;
description: string;
slug: string;
price: number;
}
interface ProductPageProps {
product: Product;
}
const ProductPage: NextPage<ProductPageProps> = ({ product }) => {
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>Price: ${product.price}</p>
</div>
);
};
export const getServerSideProps: GetServerSideProps = async (context) => {
const { slug } = context.params;
// Fetch the product data based on the slug
const product = await fetchProductData(slug as string);
return {
props: {
product,
},
};
};
async function fetchProductData(slug: string): Promise<Product> {
// Simulate fetching product data from an API or database
const products: Product[] = [
{ id: 1, name: 'Product 1', description: 'Description of Product 1', slug: 'product-1', price: 100 },
{ id: 2, name: 'Product 2', description: 'Description of Product 2', slug: 'product-2', price: 200 },
];
return products.find((product) => product.slug === slug);
}
export default ProductPage;
In this example:
The
getServerSideProps
function is an async function that runs on the server side for each request. It receives acontext
parameter that contains information about the request, including the dynamic route parameters.We extract the
slug
parameter fromcontext.params
and use it to fetch the product data. In a real-world scenario, you would fetch the data from an API or a database.The fetched product data is then passed as props to the
ProductPage
component, which renders the product details.Since this is server-side rendering, the product data is fetched and the page is rendered on the server before being sent to the client. This ensures that the page is fully populated with data when it's delivered to the client, which is beneficial for SEO and initial page load performance.
By using getServerSideProps
in combination with dynamic routing, you can fetch and render data based on dynamic route parameters with server-side rendering in Next.js.
Navigation
To navigate to a product page, you can use the Link
component from next/link
in your product listing or any other component:
import { NextPage } from "next";
import Link from "next/link";
const ProductList: NextPage = () => {
const products = [
{ id: 1, name: "Product 1", slug: "product-1" },
{ id: 2, name: "Product 2", slug: "product-2" },
];
return (
<ul>
{products.map((product) => (
<li key={product.id}>
<Link href={`/products/${product.slug}`}>{product.name}</Link>
</li>
))}
</ul>
);
};
export default ProductList;
In the ProductPage
component, we're using the useRouter
hook to access the slug
parameter from the URL. We then use this slug
to fetch the product data in a useEffect
hook. When a user clicks on a product link, they will be navigated to the corresponding product page, where the product details are displayed.
Catch-all Segments
Catch-all segments in Next.js are a powerful feature that allows you to match routes with multiple path segments using a single dynamic route. This is particularly useful when you want to create a route that can handle a variable number of segments, such as in the case of a documentation page where the URL structure represents the hierarchy of the documentation.
How Catch-all Segments Work
To create a catch-all route, you use the [...slug]
syntax in your file name. The ...
indicates that it's a catch-all segment, and slug
is the name of the parameter that will hold the array of path segments.
For example, if you have the following file structure:
pages/
└── docs/
└── [...slug].tsx
The file pages/docs/[...slug].tsx
will match any route that starts with /docs/
, regardless of the number of segments that follow. The segments after /docs/
will be captured in an array and passed to the page component as a query parameter named slug
.
Accessing the Segments in Your Component
In your page component, you can access the slug
parameter using the useRouter
hook from next/router
. The slug
parameter will be an array containing all the segments of the path.
Here's an example of how you might use this in a documentation page:
import { useRouter } from 'next/router';
const Documentation = () => {
const router = useRouter();
const { slug } = router.query;
// You can use the slug array to fetch the appropriate documentation content
// const content = fetchDocumentationContent(slug);
return (
<div>
<h1>Documentation</h1>
<p>Current Path: /docs/{slug.join('/')}</p>
{/* Render the documentation content here */}
</div>
);
};
export default Documentation;
In this example, the slug
array is used to reconstruct the current path and could also be used to fetch the appropriate documentation content based on the hierarchy represented in the URL.
Use Cases for Catch-all Segments
Catch-all segments are particularly useful in scenarios where you have a hierarchical structure that needs to be represented in the URL, such as:
Documentation: As shown in the example, you can use catch-all segments to create a documentation site where the URL structure represents the hierarchy of the documentation topics.
File Browsing: If you're creating a file browser or a similar application, catch-all segments can be used to represent the path to a file or directory.
Nested Categories: In an e-commerce site, you might use catch-all segments to handle nested categories, where the URL structure represents the category hierarchy.
Catch-all segments provide a flexible way to handle complex routing scenarios in Next.js, allowing you to create more dynamic and scalable applications.
Optional Catch-all Segments
Optional catch-all segments are similar to catch-all segments, but they also match when there are no additional path segments. This is useful for creating a route that can handle a variable number of segments, including zero. For example, you might have a blog route that can show a list of posts or an individual post:
File structure:
pages/
└── blog/
└── [[...slug]].tsx
In pages/blog/[[...slug]].tsx
, slug
will be undefined
or an array of segments:
import { useRouter } from 'next/router';
const Blog = () => {
const router = useRouter();
const { slug } = router.query;
// Fetch the blog content based on the slug array or show the blog list if slug is undefined
// const blogContent = slug ? fetchBlogContent(slug) : fetchBlogList();
return <div>Path: /blog{slug ? '/' + slug.join('/') : ''}</div>;
};
export default Blog;
Conclusion
Dynamic routing in Next.js provides a flexible and powerful way to handle a variety of URL patterns in your application. By understanding how to use dynamic routing effectively, you can create more scalable and maintainable applications. Whether you need basic dynamic routes, nested routes, catch-all segments, or optional catch-all segments, Next.js has you covered.