Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use one product catalog as the source for dynamic product routes, product-specific metadata, and sitemap entries. In the Next.js App Router, a route such as app/products/[slug]/page.tsx defines the product URL pattern; generateStaticParams maps catalog records to build-time routes, while generateMetadata and app/sitemap.ts use the same records for SEO metadata and discovery.

This guide covers the App Router. The Pages Router uses different conventions. The examples assume products are available from a shared data module; a fetched data source can use the same overall pattern.

1. Keep one canonical product source

Route generation, page rendering, metadata, and sitemap generation should read the same product records and use the same canonical slugs. That prevents a product page from existing under one slug while the sitemap advertises another.

For a small catalog known at build time, the source can be a local JSON file imported by a shared helper. A remote data source can also work. Next.js documentation demonstrates fetching data, but does not prescribe a JSON schema, file layout, validation library, or slug-normalization policy. Choose those to fit the project and ensure all consumers apply the same rules.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For example, records might include a stable slug, a display name, a description, and—only if maintained reliably—a modification date. The fields are illustrative, not a required Next.js schema.

2. Define the dynamic product route

Create app/products/[slug]/page.tsx. The bracketed folder defines a dynamic route segment, so a product with the slug blue-mug is served at /products/blue-mug. The parameter key is the folder name: [slug] produces a slug parameter.

import { products } from '@/lib/products'

export function generateStaticParams() {
  return products.map((product) => ({ slug: product.slug }))
}

type PageProps = {
  params: Promise<{ slug: string }>
}

export default async function ProductPage({ params }: PageProps) {
  const { slug } = await params
  const product = products.find((item) => item.slug === slug)

  if (!product) {
    // Use the framework's not-found handling in the real page.
  }

  return (
    <main>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </main>
  )
}

The example shows the current App Router documentation’s promise-based params typing. Match the exact params type and access pattern to the Next.js version installed in your project. For missing or deleted products, use notFound() from next/navigation rather than assuming every requested slug exists.

Provide the matching parameter keys

Each object returned from generateStaticParams must use the dynamic segment names in the route. For app/products/[category]//page.tsx, for instance, each object needs both category and product. The function supplies paths for Next.js to statically generate at build time. The Next.js generateStaticParams reference describes it as a way to statically generate routes at build time instead of on demand at request time.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

3. Choose which product routes to generate

Generating every known slug is straightforward when the catalog is manageable and the complete set is available at build time. If building every page is undesirable, return a subset and decide what should happen when a request arrives for a slug not included in that set. Next.js does not prescribe a universal catalog-size threshold or provide a benchmark that determines the right choice.

Approach What it means Decision to make
Return all known slugs Next.js receives a parameter object for each product and can generate each corresponding route at build time. Use when you want all catalog routes included in the build output and have the records available then.
Return a subset Only selected slugs are generated from the returned parameter objects at build time. Decide whether other slugs should be rendered on demand or treated as unavailable, and configure the route accordingly.

Control omitted-slug behavior

The route segment option dynamicParams controls whether paths not returned by generateStaticParams are served on demand or treated as unavailable. Set it deliberately so that the route’s runtime behavior matches the catalog and deployment design. See the official reference for the interaction with generated parameters.

generateStaticParams is not called again during ISR. If products can be added after deployment, runtime revalidation requires appropriate route configuration; a build-time list alone does not refresh itself. The function runs during development as routes are navigated, and during a build before the corresponding layouts and pages are generated.

There is also a version- and mode-sensitive caveat: with Cache Components, current documentation requires dynamic routes to receive at least one parameter from generateStaticParams; returning an empty array causes a build error. Verify this behavior against the installed Next.js version and configuration before relying on an empty build-time list. Details are in the function reference.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

4. Generate product-specific metadata

When a title or description depends on the product, export generateMetadata from the route and look up the same record by slug. Use the static metadata export instead when the metadata does not depend on a product or request.

import type { Metadata } from 'next'
import { products } from '@/lib/products'

type Props = {
  params: Promise<{ slug: string }>
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params
  const product = products.find((item) => item.slug === slug)

  if (!product) {
    return { title: 'Product not found' }
  }

  return {
    title: product.name,
    description: product.description,
  }
}

Adapt the missing-product metadata behavior to the page’s not-found handling, and avoid emitting metadata from a mismatched record. The Next.js metadata documentation explains the metadata and generateMetadata conventions. It also states that matching fetch requests are memoized across generateMetadata, generateStaticParams, layouts, pages, and Server Components. For a non-fetch source such as an imported JSON module or custom data layer, consider centralizing the lookup in a shared helper or using React cache as documented there.

5. Build a sitemap from the same slugs

Add app/sitemap.ts and return absolute product URLs constructed from the canonical slugs. The sitemap should describe the routes your application intends to expose, not a separately maintained list that can drift from the catalog.

import type { MetadataRoute } from 'next'
import { products } from '@/lib/products'

const siteUrl = process.env.SITE_URL!

export default function sitemap(): MetadataRoute.Sitemap {
  return products.map((product) => ({
    url: new URL(`/products/${product.slug}`, siteUrl).toString(),
  }))
}

Set SITE_URL to the site’s configured absolute base URL for the deployed environment. The placeholder assumes the value is present; validate it in project configuration rather than shipping a missing or incorrect base URL. The official convention is a default-exported function that returns a URL array; see the Next.js sitemap file convention.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Include modification dates only when they are trustworthy

If each record has a dependable last-modified date, map it to the sitemap entry’s lastModified value. Do not fill this field with build time or another invented date simply to populate it. The sitemap reference documents the supported entry fields.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

6. Split a large sitemap when needed

Next.js documentation states a limit of 50,000 URLs per sitemap. If a catalog needs more than one sitemap, you can organize sitemap files by route segment or use generateSitemaps to create sitemap identifiers and separate URL groups.

Option How it works Useful when
Segmented sitemap files Place sitemap files under route segments to divide sitemap coverage. The route structure provides a natural, stable way to separate URL groups.
generateSitemaps Return sitemap identifiers, then generate a URL group for each identifier. You need explicit partitions of a larger catalog.

The Next.js generateSitemaps reference demonstrates dividing product records into batches. Its numeric example uses an inclusive lower bound and an exclusive upper bound. Adapt that pattern to the actual data source: do not assume product IDs are sequential, and make each partition deterministic so it neither duplicates records nor skips boundary items.

7. Keep local JSON and fetched data consistent

A local JSON import is convenient when the catalog is available during the build and changes are deployed with the application. A fetched source can provide records independently of the code deployment, but page generation, metadata generation, and sitemap generation still need a consistent view of those records.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Data source Strength What to account for
Local build-time JSON Simple shared input for static generation and all catalog-driven outputs. Changes become available through the build/deployment workflow; ensure all consumers import the same canonical data and slug rules.
Fetched data Works with a catalog served by an external data source. Consider freshness and consistency across route params, page content, metadata, and sitemap generation. Matching Next.js fetch calls are memoized in the documented rendering contexts.

There is no universal threshold in the framework documentation for moving from local JSON to fetched data, or for choosing all-route prerendering over a subset. Make that choice based on the catalog’s update and deployment needs, and configure omitted-route behavior to match it.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.