{"slug": "i-tried-to-catch-next-js-contradicting-itself-it-never-did", "title": "I tried to catch Next.js contradicting itself. It never did.", "summary": "A developer built Router Rosetta, a retrieval-augmented agent that identifies whether a Next.js code snippet or question belongs to the Pages Router or the App Router and translates it to the other, citing a Sanity-hosted knowledge base entry for every claim. The tool is instructed not to answer from the model's own memory of Next.js, and it explicitly refuses out-of-scope questions such as Redis session storage with a custom Kubernetes ingress rather than guessing. It draws on three nextjs.org documentation sources covering 21 Pages Router pages and 20 App Router pages.", "body_md": "*This is a submission for the [Sanity Challenge, Path One: Ship an Agent That Queries Real Content](https://dev.to/challenges/sanity-2026-09-16)*\n\nNext.js has two routers. The Pages Router and the App Router answer the same questions in incompatible ways, and both sets of documentation are live, indexed, and correct. Neither page carries a banner saying \"this is the old way.\" Search does not tell you which one you landed on.\n\nSo an AI assistant or an old tutorial hands you `getServerSideProps`, you paste it into an App Router project, and Next.js rejects it. The answer was not wrong. It was for the other router.\n\n**Router Rosetta** takes a snippet or a question, identifies which router it belongs to, and gives you the equivalent in the other one. Every claim carries the documentation entry it came from, and every equivalence carries a citation to the official migration guide.\n\nIt also refuses. If the knowledge base does not cover something, it says so rather than filling the gap from the model's memory of Next.js.\n\n**Live:** [https://router-rosetta.vercel.app](https://router-rosetta.vercel.app)\n\nPaste code, or use one of the four examples on the page.\n\nGiven this:\n\n``` js\nexport async function getServerSideProps() {\n  const res = await fetch('https://api.example.com/posts')\n  return {props: {posts: await res.json()}}\n}\n```\n\nIt answers:\n\nThis code uses **Pages Router**, as identified by the use of `getServerSideProps` [data_fetching/pages_static].\n\nIn the App Router, `getServerSideProps` is replaced by fetching data directly inside an `async` Server Component page [migration, data_fetching/app_router]. In the App Router, `fetch` calls inside Server Components run on the server during rendering [data_fetching/app_router], mapping to `getServerSideProps` dynamic request behavior [migration].\n\nThen it writes the App Router version, citing the target entry and the migration\n\nguide for the equivalence.\n\nAsk it something out of scope:\n\nHow do I configure Redis session storage with a custom Kubernetes ingress in Next.js?\n\nThe knowledge base does not contain information on configuring Redis session storage or custom Kubernetes ingress routing in Next.js.\n\nThe only Kubernetes and deployment details supported by the knowledge base are:\n\n- Next.js supports deployment to Docker-compatible providers, including Kubernetes, using either a Node.js server or Docker container build [setup/configuration].\n- For Docker and containerized environments like Kubernetes, Next.js provides an\n`output: \"standalone\"` configuration mode in `next.config.js` to create a minimal production image containing only the required runtime files [setup/configuration].\nBecause Redis session storage and custom Kubernetes ingress configurations are not covered in the knowledge base, no router-specific mapping or implementation code can be provided.\n\nTranslates Next.js code and questions between the **Pages Router** and the **App Router**, citing a knowledge base entry for every claim.\n\nPaste `getServerSideProps` and it tells you this is Pages Router code, gives you the async Server Component equivalent, and cites the docs entry behind each statement. Ask \"how do I read search params in each router?\" and it answers for both. Every factual claim carries a citation chip — the point is that you can check the answer rather than trust it.\n\nThe model is instructed not to answer from its own memory of Next.js. If the knowledge base does not cover a mapping, it says so instead of guessing.\n\n```\nnpm install\ncp .env.local.example .env.local   # then fill in the values below\nnpm run dev\n```\n\nOpen [http://localhost:3000](http://localhost:3000).\n\nThe two Sanity variables are required. At least one model provider key is also required…\n\nThree website sources, all on [nextjs.org](https://nextjs.org/):\n\n| Source | Pages | Role | \n|---|---|---|\n| `/docs/pages/building-your-application` | 21 | The superseded way | \n| `/docs/app/getting-started` | 20 | The current way | \n| `/docs/app/guides/migrating/app-router-migration` | 1 | The arbiter | \n\n42 pages, no dataset or uploaded files.\n\n**One setting mattered more than everything else: Max depth.** Left empty, the crawler reads the seed URL and follows nothing. My first build pulled 7 pages out of a 38 page target and I spent a while blaming my include patterns. Setting Max depth to 3 took it to 42.\n\nContext turned those 42 pages into 16 entries, and it organised them into **pairs**:\n\n| Topic | Pages Router | App Router | \n|---|---|---|\n| Data fetching | `data_fetching/pages_static` | `data_fetching/app_router` | \n| Routing | `routing/pages_router` | `routing/app_router` | \n| Rendering | `rendering/pages_router_modes` | `caching` | \n\nPlus `migration`, holding the deprecated-to-current API mapping.\n\nEach entry also carries an `excludes:` line pointing at its counterpart:\n\n```\ndata_fetching/app_router [peripheral]\n  App Router data fetching: Server Components with fetch API or ORM/database...\n  excludes: Pages Router methods (getStaticProps, getStaticPaths,\n            getServerSideProps); cache/revalidation config.\n```\n\nThat line is the whole product: the knowledge base records where the other half of each answer lives, and I did not model that pairing myself. The build wrote it from the source material.\n\nThe endpoint serves Knowledge Base mode, so two tools:\n\n`initial_context` for the outline, which is small enough to hold for a whole conversation`knowledge_base_read` for the entries themselves, several paths at a time\nThe system prompt forces the pairing to be used rather than hoped for:\n\nRead the entry for that router AND its paired entry for the other router. Read\n\nthe migration entry whenever you assert an equivalence.\n\nWithout that line, the model answers from whichever entry it reads first and produces a confident, one-sided answer.\n\nKeyword search for `getServerSideProps` returns the Pages Router documentation. It is the right page, current and accurate, and it will not tell you that a newer router replaced it or what the replacement looks like. The answer requires knowing which of two parallel systems a claim belongs to and what it maps onto, a relation between sources rather than a string inside any one of them.\n\nI started by trying to surface **contradictions** between the two routers, because the Sanity docs describe conflict detection and it sounded like a strong demo.\n\nThe first build, the starved one with 7 pages, raised a real conflict about `getStaticPaths` `fallback` values.\n\nThen I fixed the crawl, rebuilt with all 42 pages, and got **zero conflicts.**\n\nThat result is correct. With the full picture, including the migration guide explaining that one router supersedes the other, Context concluded these are two coherent systems rather than one system contradicting itself. More context produced better reconciliation. The conflict I had been excited about was an artifact of an incomplete index.\n\nNext.js does not contradict itself, it documents two things in parallel, and that only became visible once the crawl was complete. I kept the same 42 pages and the same paired structure, and changed the product from adjudication to\n\ntranslation.\n\n**Stack:** Next.js 16, Vercel AI SDK v7, `@ai-sdk/mcp` connecting to Context MCP, Groq as the model with Gemini as a fallback.\n\n**The agent is provider-agnostic, and that is the point.** All the domain knowledge lives in the knowledge base rather than in the model. Neither provider knows anything about Next.js routing beyond what it reads through MCP.\n\n**Free tiers forced real engineering, not just a config swap.** Gemini's free tier caps at 20 requests per day per model, which a demo exhausts in minutes, so Groq is the primary and Gemini is the fallback. Three things make that survivable in production: the client mirrors the limit with a sliding window so you aretold before a request is wasted, first-turn answers are cached for a day so the example buttons do not re-derive the same output, and a failed request actually switches provider mid-run rather than just erroring, using the AI SDK's `prepareStep` so the swap sticks for the rest of the agent's steps.\n\n**Errors were invisible until I made them loud.** The AI SDK's default failure message is \"An error occurred,\" identical for every cause. Wiring `onError` to report the real cause was one of the first things worth doing, well before therate limits above ever came up.\n\n**Project ID:** `3bpjqog6`\n\n**Dataset:** `production` (public)\n\n**Knowledge Base ID:** `kbm133o2qNEI`\n\nThe project is deliberately empty. The knowledge base is built entirely from website sources, so no dataset content was needed. The structure worth looking at is in the Knowledge Base entries, not in the Content Lake.", "url": "https://wpnews.pro/news/i-tried-to-catch-next-js-contradicting-itself-it-never-did", "canonical_source": "https://dev.to/ajipelumi/i-tried-to-catch-nextjs-contradicting-itself-it-never-did-3e63", "published_at": "2026-09-25 08:24:51+00:00", "updated_at": "2026-09-25 08:30:37.083270+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "large-language-models"], "entities": ["Next.js", "Router Rosetta", "Sanity", "Vercel", "Pages Router", "App Router"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/i-tried-to-catch-next-js-contradicting-itself-it-never-did", "markdown": "https://wpnews.pro/news/i-tried-to-catch-next-js-contradicting-itself-it-never-did.md", "text": "https://wpnews.pro/news/i-tried-to-catch-next-js-contradicting-itself-it-never-did.txt", "jsonld": "https://wpnews.pro/news/i-tried-to-catch-next-js-contradicting-itself-it-never-did.jsonld"}}