Next.js App Router silently ignored my 1,200 generated pages. Here's what I got wrong.
I spent about a week building factorcalculator.org — a free math tool that finds the factors, factor pairs, prime factorization and divisors of any number, with the full working shown instead of just an answer. Six hand-built calculator pages, plus 1,200 generated pages ( /factors-of-1/ through /factors-of-1200/ ). Next.js 16 App Router, Tailwind, static export, deployed to Cloudflare Pages. Most…
I spent a week building factorcalculator.org, a free math tool that finds various properties of numbers. The site featured six hand-built calculator pages and 1,200 generated pages (factors-of-1 through factors-of-1200). Next.js 16 App Router, Tailwind, static export, and deployment on Cloudflare Pages were used. Three issues arose, two of which are bugs where everything seemed to work until it didn't. Here's what went wrong, along with a postscript on traffic.
1. The App Router doesn't support partial dynamic segments. This caused significant delays. I wanted URLs like /factors-of-84/. The folder name app/factors-of-[number]/page.js seemed appropriate. However, it only generated a single static route at /factors-of-%5Bnumber%5D/. My generateStaticParams didn't run at all. The App Router only treats a path segment as dynamic if the entire segment is in brackets.
Thus, 'factors-of-[number]' is a folder with brackets in its name, not a dynamic path. The solution was to parse the prefix: create a function to extract the 'number' from the URL, check its validity, and return it. This function generates static params for valid numbers within a specified range. Remember, static routes still win; so even with dynamic parameters, hand-built pages keep their own routes.
The dynamicParams = false setting ensures only known URLs render, preventing infinite variants. The leading-zero guard is crucial, as variations like /factors-of-012/ can cause issues.
2. Hydration mismatch kept occurring. The calculator had an input and a result; server and client renders disagreed. First, the obvious: server rendered "1,234" while the client rendered "1 234" or "1.234" depending on locale. This was fixed by using toLocaleString(). However, the issue persisted in other places as the component grew.
The fix was to stop attempting to make server and client renders match for interactive widgets and instead ensure they match by rendering nothing interactive until after the component mounts. This prevents hydration mismatches, as the server HTML and first client render are byte-identical once mounted.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.