In the Next.js App Router, a JSON product catalog can drive dynamic product pages, product-specific metadata, and sitemap entries from one shared data source. Define a route such as app/products/[slug]/page.tsx, map catalog records to route parameters with generateStaticParams, and use those same records in generateMetadata and app/sitemap.ts. This guide covers the App Router; the Pages Router uses different conventions.
1. Keep the catalog available to every part of the route
Use one canonical source for product records so route generation, page rendering, metadata, and sitemap URLs agree. For a small catalog built with the site, that may be a local JSON module; for data that changes independently of deployments, it may be a fetched source. Next.js documents the framework mechanisms, but does not prescribe a JSON schema, file layout, slug policy, or validation library.
As an Amazon Associate I earn from qualifying purchases.
For illustration, assume each product has a unique, stable slug, a name, a description, and optionally a trustworthy updatedAt value. Validate the actual data according to the needs of your project; these fields are an example, not a required Next.js schema.
2. Create a dynamic product route
A bracketed directory creates a dynamic route segment. In app/products/[slug]/page.tsx, the segment name [slug] determines the route parameter key. A product with slug trail-pack is served at /products/trail-pack.
#1 Best Overall
The following example assumes a shared getProducts() helper that returns the canonical catalog, whether that helper reads local JSON or fetches data:
import { notFound } from 'next/navigation'
import { getProducts } from '@/lib/products'
export async function generateStaticParams() {
const products = await getProducts()
return products.map((product) => ({ slug: product.slug }))
}
export default async function ProductPage({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const products = await getProducts()
const product = products.find((item) => item.slug === slug)
if (!product) notFound()
return (
<main>
<h1>{product.name}</h1>
<p>{product.description}</p>
</main>
)
}
Current App Router documentation types params as a promise in page examples. Match the type and access pattern supported by the Next.js version installed in your project; older versions may use different conventions. The notFound() check handles a request for a slug that has no catalog record rather than assuming every requested URL is valid.
Nested route segments
For a route such as app/products/[category]/[product]/page.tsx, each object returned by generateStaticParams must provide both keys, for example { category: product.categorySlug, product: product.slug }. Parameter names must match the bracketed directory names. See the Next.js generateStaticParams reference.
Recommended Free Tools
Rank #2
3. Choose which product routes to prerender
generateStaticParams returns the dynamic route parameters Next.js should statically generate during the build. The official description says: “The generateStaticParams function can be used in combination with dynamic route segments to statically generate routes at build time instead of on-demand at request time.”
You can return every known slug or only a subset. Next.js does not specify a universal catalog-size threshold for choosing between these approaches; consider your build needs and how the remaining routes should behave.
| Approach | What happens at build time | Decision to make |
|---|---|---|
| Return all product slugs | All returned product routes are generated at build time. | Whether the catalog and build workflow make generating every known page appropriate. |
| Return a subset | Only the returned product routes are generated at build time. | Whether routes omitted from the list should be served on demand or treated as unavailable. |
The route segment option dynamicParams controls the behavior for paths not returned by generateStaticParams. Check the current reference and your route configuration to choose the behavior you intend.
Rank #3
Builds, development, and revalidation
During a build, generateStaticParams runs before the corresponding layouts and pages are generated. In development, it runs as routes are navigated. The function is not called again during ISR, so do not rely on ISR alone to refresh its generated parameter list; configure the route and data-refresh approach for your update requirements. The details depend on your Next.js version and route configuration.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallCache Components considerations
Next.js documentation describes a Cache Components-specific constraint: dynamic routes require at least one parameter from generateStaticParams, and returning an empty array causes a build error. This behavior depends on the version and mode in use, so verify it against the documentation for your installed version.
4. Generate product-specific metadata
Export generateMetadata from the product page when a title or description depends on the requested product. Resolve the same canonical record by slug and return a Metadata object. If metadata is identical across products and does not depend on route or request information, use the static metadata export instead.
Rank #4
import type { Metadata } from 'next'
import { getProducts } from '@/lib/products'
type Props = {
params: Promise<{ slug: string }>
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const products = await getProducts()
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-record metadata behavior to the rest of your route handling. When data is fetched with matching fetch requests, Next.js memoizes those requests across generateMetadata, generateStaticParams, layouts, pages, and Server Components. For a non-fetch source such as a local JSON helper, consider sharing a data-access helper or using React cache as described in the Next.js metadata documentation.
5. Build the sitemap from the same canonical slugs
Create app/sitemap.ts and return absolute product URLs using the same slugs as the route. Configure the site’s absolute base URL for your deployment rather than constructing inconsistent product paths in multiple places.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →import type { MetadataRoute } from 'next'
import { getProducts } from '@/lib/products'
const siteUrl = process.env.SITE_URL!
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const products = await getProducts()
return products.map((product) => ({
url: new URL(`/products/${product.slug}`, siteUrl).toString(),
...(product.updatedAt
? { lastModified: new Date(product.updatedAt) }
: {}),
}))
}
Set SITE_URL to the site’s actual absolute origin in the deployment environment. Include lastModified only if the catalog has a reliable modification date; omitting it is better than inventing one. Next.js’s sitemap convention uses a default-exported function that returns a URL array.
Using the same catalog and canonical URL rules for pages and sitemap entries helps prevent links to nonexistent routes or omitted products. The sitemap should reflect the URLs your application actually intends to serve.
6. Split a large sitemap into multiple files
Next.js documentation states a limit of 50,000 URLs per sitemap. For a catalog too large for one sitemap, you can use sitemap files nested under route segments or use generateSitemaps to create separate groups. The generateSitemaps reference demonstrates dividing product records into batches.
Partition records deterministically: each product should appear once, with no gaps at batch boundaries. The documentation’s numeric example uses an inclusive lower bound and an exclusive upper bound; if your catalog IDs are not sequential, partition the actual records or query results rather than assuming they are.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsQuick Recap
7. Keep JSON, routes, metadata, and sitemap consistent
- Use matching parameter keys: return
slugfor[slug], and provide every key for nested dynamic segments. - Use one canonical record lookup: page content and metadata should resolve products from the same source and slug rules.
- Handle missing products: a URL request may not match a current catalog entry; decide the page and metadata behavior deliberately.
- Choose the generated route set: returning a subset requires a decision about unlisted routes and the
dynamicParamssetting. - Match sitemap URLs to route URLs: use the same base URL and slug construction, and only emit modification dates supported by the data.
- Recheck version-sensitive behavior: App Router parameter typing, Cache Components constraints, and route behavior should match the installed Next.js version.
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.




