How a Next.js 16 Default Quietly De-Indexed My Site (and the One-Line Fix)
My organic traffic fell off a cliff after a routine framework upgrade — no Google penalty, no hack, just pages silently dropping out of the index. The cause was a single rendering default in Next.js 16. Here is how I tracked it down and the one line that fixed it.
This is a postmortem of a self-inflicted SEO wound. QTNest is a static-first site built on the Next.js App Router: a few hundred quiz, tool, and blog pages, almost all of them generated from data at build time. For a long time it just worked — pages were prerendered, Google crawled them, and they ranked. Then I upgraded to Next.js 16, deployed, and over the following weeks watched a large share of my pages quietly disappear from Google. There was no manual action, no penalty, no security warning. The pages still loaded perfectly in my browser. They had simply stopped being indexable. The culprit turned out to be a single rendering default, and the fix was one line per route.
The symptom: traffic sliding with no obvious cause
The first thing I noticed was not an error — it was a slow, steady decline in organic clicks in Google Search Console. Not a cliff in a single day, which is what a penalty usually looks like, but a grind downward over a couple of weeks as Google re-crawled the site and progressively dropped pages. That gradual shape is exactly why it is so easy to miss: there is no single bad deploy to point at, and day-to-day the dip looks like ordinary search-traffic noise.
When I finally opened the Pages report in Search Console, the story was clear. Pages that had been indexed for months were moving into the "Not indexed" bucket, and the reason attached to them was the tell: "Page with redirect" and "Discovered – currently not indexed." Google was being told, in effect, to go somewhere else every time it tried to fetch certain URLs.
What Search Console was actually telling me
The key diagnostic was the URL Inspection tool. I pasted in one of the affected URLs — a tool page that loaded instantly and correctly when I visited it myself — and Search Console reported that the crawled page was a redirect. The live test agreed: fetching the URL as Googlebot returned a 307 (temporary redirect) rather than a 200. So from Google's point of view, these were not real, indexable pages at all; they were URLs that bounced the crawler elsewhere, and a redirect is not something Google indexes. After enough crawl attempts that resolved to redirects, the pages aged out of the index entirely.
The confusing part — and what cost me time — was that none of this reproduced in a normal browser. Visiting the page as a human returned a clean 200 and the fully rendered content. The redirect behaviour only showed up to the crawler and to a raw, no-frills request. That mismatch is what kept me chasing the wrong theories (a bad canonical tag, a stale sitemap, a CDN rule) before I checked the one thing that mattered: the raw HTTP status code of the page itself.
The cause: a changed rendering default in Next.js 16
Here is the root cause. My dynamic route segments — things like /tools/[tool] and /quiz/[slug] — use generateStaticParams to enumerate every page at build time. Under the App Router I had always treated that as equivalent to "prerender all of these as static HTML." For the versions I had been running, that assumption held in practice.
In Next.js 16 it no longer held the way I expected. With my configuration, the framework did not commit those routes to fully static output at build time; it left them to be resolved dynamically on request. The practical, observable consequence on my host was that the first crawl of an un-prerendered path returned a 307/308 redirect rather than a straight 200 with HTML. A human almost never notices a single 307 — the browser follows it instantly and the page appears. Googlebot, on the other hand, treats the URL as "this is a redirect," not "this is a page," and indexes it accordingly: which is to say, not at all.
The deeper lesson is not about one version number. It is that framework defaults around rendering and caching change between major releases, and a default that flips from "static by default" to "dynamic unless told otherwise" can be completely invisible in local development and in casual browsing — while being catastrophic for indexation. Your code can be unchanged and correct, and still produce a different crawlable surface after an upgrade.
The one-line fix
The fix was to explicitly opt each affected route into static rendering by adding a single line to the route file:
export const dynamic = 'force-static'
That route-segment config tells Next.js, unambiguously, to render the page to static HTML at build time and serve it as a static asset. With it in place, a request for the page — from a browser or from Googlebot — returns a 200 with the prerendered HTML instead of a redirect. I added it to every dynamic route segment (tools, quizzes, blog posts) and to the listing pages, rebuilt, and verified the output. The principle is simple: if a page does not depend on per-request data, it should be static, and on a major upgrade you should state that explicitly rather than rely on the framework inferring it.
How to check whether your own site is affected
You do not need to wait for traffic to drop to find this. The fastest check is a raw request for one of your dynamic pages: run curl -I against the URL (or use any tool that shows you the bare HTTP status) and look at the first line. A healthy static page returns "200 OK." If you see "307 Temporary Redirect" or "308 Permanent Redirect" on a page that has no business redirecting, that is the smoking gun. Crucially, do this against your deployed production URL, not localhost, because the behaviour depends on how the page is built and served.
In Google Search Console, the corroborating signal lives in two places. The Pages report will show a rising count under "Page with redirect" and "Discovered – currently not indexed." And the URL Inspection tool, run as a live test on an affected page, will report that the crawled page is a redirect even though the page renders fine for you. If both the raw status code and Search Console agree that your content pages are redirects, you have the same problem, and the same one-line fix applies.
Why this class of bug is so easy to miss
Three things conspire to hide it. First, it is invisible in the browser: humans follow redirects transparently, so every page looks healthy when you click around your own site. Second, it is invisible in CI: the build succeeds, there are no type errors, and nothing crashes — the site is "working," just not in the one dimension search engines care about. Third, the damage is delayed and gradual: Google re-crawls on its own schedule, so the traffic impact shows up weeks after the deploy that caused it, long after you have moved on to other work and stopped associating the decline with that upgrade.
The takeaway I now operate by: after any major framework upgrade, treat indexability as a first-class part of the release checklist. Building successfully is necessary but not sufficient. The real questions are what HTTP status your important pages return to a crawler, and whether Search Console still considers them indexable a week later.
A short checklist to avoid it
Concretely: after a major Next.js (or any framework) upgrade, build and serve the production output locally, then curl -I a sample of your most important routes and confirm they return 200, not 307/308. Be explicit about rendering intent — if a page can be static, mark it static rather than trusting the default to stay the same across versions. Watch the Search Console Pages report weekly for a fortnight after the deploy, specifically for growth in "Page with redirect" and "Discovered – currently not indexed." And read the migration guide for changes to rendering and caching defaults, because those are precisely the changes that pass every test and still quietly cost you traffic.
It is a humbling kind of bug: nothing was broken, nothing threw, and the code was arguably correct the whole time. One explicit line — export const dynamic = 'force-static' — was the difference between pages Google could index and pages it quietly threw away.
About QTNest
QTNest is the static-first site this happened to — a collection of free, browser-based tools and quizzes, all prerendered at build time (now explicitly so). If you want to see the kind of pages this was about, the tools below run entirely in your browser with no sign-up. And if you found the debugging useful, the one habit worth stealing is checking your production pages' raw status codes after every major upgrade.
Related Tools
JSON Validator & Formatter
Validate JSON for errors and format it with custom indentation — all in one tool.
UUID Generator
Generate version 4 UUIDs (Universally Unique Identifiers) instantly. Generate up to 20 at once and copy with one click.
Word Counter
Count words, characters, sentences, and paragraphs in your text instantly.