skip to content

An e-commerce Next.js App Router app serves 50,000 product pages from a /products/[slug] route. How do you decide whether to prerender all of them at build time, and what are your options when a full build takes too long?

level: seniorimportance: should knowfreq 48%

answer

  1. the array size is the build size
  2. hot subset versus whole catalogue
  3. unlisted params still render on demand
  4. cold first hit on the long tail
  5. closed URL space changes the answer

basics

~20 s

Prerender the whole catalogue only if the build stays within an acceptable deploy time and the slug set is known. Otherwise return just the high-traffic slugs from generateStaticParams and let the rest render on first request and be cached from then on.

solid answer

~50 s

For a dynamic segment, `generateStaticParams` decides which slugs get built. Returning all 50,000 means the build renders 50,000 pages, so deploy time and build cost scale with the catalogue — fine at a few thousand, painful when every hotfix waits twenty minutes. The usual answer is a hot subset: return the top few hundred slugs by traffic or recency, and leave the tail to be rendered on demand at first request and cached afterwards, which is the default behaviour for params not returned. You trade a slower first hit on cold long-tail pages — measure that, it is usually one upstream call — for a build that stays proportional to what actually gets traffic. If the slug set is unknowable or the catalogue churns constantly, return an empty array and let everything fill in on demand. And if unknown slugs must 404 rather than render, set `dynamicParams` to false so the listed set is exhaustive.

code

tsx · 16 lines
tsx
// app/products/[slug]/page.tsx
// Prerender only the hot subset; the rest render on first request.
export async function generateStaticParams() {
  const slugs: string[] = await getTopSellingSlugs(500)
  return slugs.map((slug) => ({ slug }))
}

export default async function ProductPage({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
  const product = await getProduct(slug)
  return <ProductView product={product} />
}

go deeper

for a junior

Know that a dynamic route needs generateStaticParams to be prerendered at all, and that whatever it returns is what the build produces.

for a middle

Explain the mechanics both ways: how the returned array drives build time, and what happens on the first request for a slug that was not in it.

for a senior

Show the measurement: per-page build cost times page count against your deploy window, traffic skew justifying a subset, and the cold-path latency you are accepting for the tail.

for a principal

Own the systemic risk — a build that grows with the catalogue eventually throttles the team's release cadence, so treat build duration as an operational budget with an owner, not an incidental number.

## What generateStaticParams actually controls A dynamic segment such as `app/products/[slug]/page.tsx` matches an unbounded set of URLs, so Next.js cannot know at build time which pages to produce unless you tell it. `generateStaticParams` is that instruction: an exported async function returning an array of param objects, each of which becomes a page rendered during the build. ```tsx // app/products/[slug]/page.tsx export async function generateStaticParams() { const slugs = await getTopSellingSlugs(500) return slugs.map((slug: string) => ({ slug })) } export default async function ProductPage({ params, }: { params: Promise<{ slug: string }> }) { const { slug } = await params const product = await getProduct(slug) return <ProductView product={product} /> } ``` Everything about the decision follows from one fact: **the size of that array is the size of your build.** Fifty thousand entries means fifty thousand renders, each with whatever upstream calls the page makes, on every deploy. ## The three positions on the dial **Build everything.** Return all slugs. Every product page is a stored file from the first request, so there is no cold-start penalty anywhere and traffic is uniformly cheap to serve. The cost is deploy latency and build-time load on whatever backend serves the product data. This is the right answer when the catalogue is bounded and the build fits comfortably in your release cadence — a hotfix that has to wait out a forty-minute build is an incident-response problem, not just an inconvenience. **Build a hot subset.** Return the slugs that matter: top sellers, recently updated items, whatever your analytics say covers most traffic. Slugs not in the list are not built; the first request for one renders it on demand and it is cached for subsequent visitors. Long-tail products therefore have a slower first hit and are fast afterwards. Given how skewed e-commerce traffic usually is, a few hundred prerendered pages can cover the large majority of requests while the build stays measured in minutes. **Build nothing.** Return an empty array. Nothing is prerendered and every page fills in on first request. This suits a catalogue that changes faster than you deploy, or one whose slug list is not cheaply enumerable at build time. You keep on-demand caching benefits without letting the build depend on catalogue size at all. ## The knob that changes the meaning of "not listed" By default, params outside the returned list are still rendered on demand. Exporting `dynamicParams` as `false` inverts that: the returned list becomes exhaustive and any other slug produces a 404. That is what you want when the URL space genuinely is closed — a fixed set of landing pages, a finished archive — and it is a foot-gun on a live catalogue, because a product added after the last deploy would 404 instead of rendering. ## How to actually decide Run the numbers rather than the intuition. 1. **Measure one page's build cost.** Render time plus upstream latency, times the number of pages, divided by build parallelism, is your build-time estimate. If that number is inside your acceptable deploy window with headroom, building everything is the simplest system and you should take it. 2. **Look at the traffic distribution.** If 300 SKUs cover 80% of page views, a hot subset gives you nearly all the benefit for under 1% of the build. 3. **Measure the cold path.** The first request for an unbuilt slug costs a render plus its upstream calls. If that is 200 ms it is a non-issue; if the product query takes two seconds, fix the query before you decide anything about rendering. 4. **Check catalogue churn.** If products are added hourly, a build-everything strategy is pinned to your deploy cadence and new items are invisible until the next release, which usually forces a subset or on-demand approach with an invalidation path for updates. ## The failure modes to name The two bad outcomes are worth stating explicitly in an interview. **Build explosion**: nobody notices until deploys creep past the point where the team stops deploying, and the fix is retroactive and disruptive. **Cold long tail**: crawler traffic and deep links hit unbuilt pages, so your synthetic monitoring on the homepage looks perfect while real users on product pages see the slow path. Both are measurable in advance, which is exactly why the interviewer is asking rather than accepting "prerender everything".

  • What breaks if you export dynamicParams as false on this catalogue route?
    The list returned by generateStaticParams becomes the complete set of valid URLs, so any slug not built at deploy time returns a 404. On a live catalogue that means every product added since the last release is unreachable until you deploy again. It is the right setting only for a genuinely closed URL space.
  • How would you decide which 500 slugs make the hot subset?
    From data, not intuition: page-view counts over a representative window, plus anything with a known upcoming spike such as a campaign landing SKU or a new release. I would also make the list a query rather than a hardcoded array, so it tracks the catalogue automatically and the subset does not quietly go stale as buying patterns shift.
  • Your build is slow but you still want every page prerendered. What do you attack first?
    Per-page cost before page count. Most catalogue builds are dominated by upstream latency, so batching or a single bulk query that all pages read from usually cuts the build far more than any rendering change. Only after that would I look at build parallelism, and only then consider trimming the set.

saying these in an interview costs you the question

  • Assumes prerendering 50,000 pages is always the right default
  • Does not connect the returned params array to build duration
  • Thinks unlisted slugs automatically 404 without configuring it
  • Ignores the cold first-request cost on unbuilt pages
  • Picks the hot subset by intuition rather than traffic data

context