This is a submission for the Sanity Challenge, Path One: Ship an Agent That Queries Real Content
Next.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.
So 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.
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.
It 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.
Live: https://router-rosetta.vercel.app
Paste code, or use one of the four examples on the page.
Given this:
export async function getServerSideProps() {
const res = await fetch('https://api.example.com/posts')
return {props: {posts: await res.json()}}
}
It answers:
This code uses Pages Router, as identified by the use of getServerSideProps [data_fetching/pages_static].
In 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].
Then it writes the App Router version, citing the target entry and the migration
guide for the equivalence.
Ask it something out of scope:
How do I configure Redis session storage with a custom Kubernetes ingress in Next.js?
The knowledge base does not contain information on configuring Redis session storage or custom Kubernetes ingress routing in Next.js.
The only Kubernetes and deployment details supported by the knowledge base are:
- Next.js supports deployment to Docker-compatible providers, including Kubernetes, using either a Node.js server or Docker container build [setup/configuration].
- For Docker and containerized environments like Kubernetes, Next.js provides an
output: "standalone"configuration mode innext.config.jsto create a minimal production image containing only the required runtime files [setup/configuration]. Because 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.
Translates Next.js code and questions between the Pages Router and the App Router, citing a knowledge base entry for every claim.
Paste 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.
The 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.
npm install
cp .env.local.example .env.local # then fill in the values below
npm run dev
Open http://localhost:3000.
The two Sanity variables are required. At least one model provider key is also required…
Three website sources, all on nextjs.org:
| Source | Pages | Role |
|---|---|---|
/docs/pages/building-your-application |
21 | The superseded way |
/docs/app/getting-started |
20 | The current way |
/docs/app/guides/migrating/app-router-migration |
1 | The arbiter |
42 pages, no dataset or uploaded files.
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.
Context turned those 42 pages into 16 entries, and it organised them into pairs:
| Topic | Pages Router | App Router |
|---|---|---|
| Data fetching | data_fetching/pages_static |
data_fetching/app_router |
| Routing | routing/pages_router |
routing/app_router |
| Rendering | rendering/pages_router_modes |
caching |
Plus migration, holding the deprecated-to-current API mapping.
Each entry also carries an excludes: line pointing at its counterpart:
data_fetching/app_router [peripheral]
App Router data fetching: Server Components with fetch API or ORM/database...
excludes: Pages Router methods (getStaticProps, getStaticPaths,
getServerSideProps); cache/revalidation config.
That 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.
The endpoint serves Knowledge Base mode, so two tools:
initial_context for the outline, which is small enough to hold for a whole conversationknowledge_base_read for the entries themselves, several paths at a time
The system prompt forces the pairing to be used rather than hoped for:
Read the entry for that router AND its paired entry for the other router. Read
the migration entry whenever you assert an equivalence.
Without that line, the model answers from whichever entry it reads first and produces a confident, one-sided answer.
Keyword 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.
I started by trying to surface contradictions between the two routers, because the Sanity docs describe conflict detection and it sounded like a strong demo.
The first build, the starved one with 7 pages, raised a real conflict about getStaticPaths fallback values.
Then I fixed the crawl, rebuilt with all 42 pages, and got zero conflicts.
That 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.
Next.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
translation.
Stack: Next.js 16, Vercel AI SDK v7, @ai-sdk/mcp connecting to Context MCP, Groq as the model with Gemini as a fallback.
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.
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.
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.
Project ID: 3bpjqog6
Dataset: production (public)
Knowledge Base ID: kbm133o2qNEI
The 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.