Back to Insights
7 min read
Chris Vince
Chris Vince

How We Moved Siro's Web App from Svelte to Next.js in Four Weeks

Gradient banner for the Svelte to Next.js migration article.

This spring we replaced Siro's web application. Every page, every feature, every permission check — rebuilt in four weeks, from an empty repository to production at app.siro.ai. The old app was built in Svelte. The new one is built in Next.js. First paint dropped from 3.5 seconds to 0.6.

But the migration isn't the story. The story is how the work got done. Most of the code was written by AI agents — Claude Code, running in parallel sessions across the team, with Claude Opus planning the work and Claude Sonnet executing it. And the reason that worked isn't that the models are impressive, though they are. It's that we designed the codebase, from the first commit, to be legible to them.

We made two bets before migrating anything: decide every architectural question up front, and treat the agent as a first-class reader of the codebase. Everything else followed from those two.


Why we left Svelte

Our old app, ClientConsole, was a client-only Svelte single-page app: a 3.6 MB JavaScript bundle with no code-splitting and no server layer. Every visit meant downloading, parsing, and executing the entire application before the first API call could even begin. Authentication, permissions, and routing all ran in the browser — the one environment we don't control.

Svelte is a good framework. It had stopped being a good fit. The limits were structural: no server layer to grow into, an ecosystem where key libraries are community ports rather than officially maintained projects, and TypeScript support that sits beside the language rather than inside it. Next.js gave us server-side rendering, streaming, granular caching, and middleware — and something less obvious, which we'll come to: a framework that AI tools understand deeply.

We wrote the case as an RFC, debated it, and committed. Greenfield. A new repository, no legacy constraints, every decision ours to make. That blank slate did more than clear away legacy code. It meant the codebase never had a phase where conventions were fuzzy. It was designed for AI from its first file.


Decide everything first

Before migrating a single page, we wrote a second RFC: twenty-six decisions covering state management, data fetching, the API layer, auth, folder structure, file naming, error handling, testing, deployment. Not principles — choices. Zustand for UI state. TanStack Query on the client. Server Actions for mutations. One rule for where a hook lives, and one rule for when it earns promotion to shared code.

26 — architectural decisions made before the first page was migrated.


The reasoning was written into the document itself: patterns that aren't agreed up front get decided implicitly by whoever writes the first implementation, and then copy-pasted everywhere. That's true of people. It's doubly true of agents, which will faithfully replicate whatever they find. Consistency isn't tidiness — it's context. One pattern for state, one pattern for fetching, one folder convention: that's what makes an agent's output land correctly the first time. And the advantage compounds with every page you add.

A week in, we'd learned the corollary: existing patterns are far better for generative AI than context. Instruction files help, but a codebase that already demonstrates the answer teaches better than any description of it — which matches what the research on in-context learning keeps finding: these models follow worked examples more reliably than instructions. So the first few pages we migrated got the closest human review of the whole project. They weren't just pages. They were the training set.

It also changed how the month felt. With the architecture settled, there was no mid-migration debate about how to build. We always knew the next move. The migration became execution.


Build for the agent

Then we made the codebase teach itself.

We placed AGENTS.md files throughout the repository — thirty-seven of them — a root file carrying the architecture and global conventions, and local files carrying the rules of each neighborhood: the data layer, server actions, tests, translations, permissions. Wherever an agent starts working, the specifics of that domain are one file away. It never has to guess where something belongs, because the answer is sitting next to the code.

File explorer showing 37 AGENTS.md files across the repository.

Thirty-seven files that tell the agents what good looks like.

We treated those files as living infrastructure, not documentation. When they grew bloated, we compacted them. And we made them load-bearing: a standards-review check runs on every pull request, cross-referencing the relevant AGENTS.md files and flagging anything that drifts from convention, alongside a second check that scores each change's risk. The same files that guide the agent writing the code are used to review it — agents auditing agents, with people making the calls.

On top of that sat MIGRATION.md — the playbook for porting one feature at a time. Its central instruction: treat the old app as a behavioral spec, not an implementation reference. The agent reads the Svelte page to learn what the feature does — the UI, the flow, the API contract, the edge cases — then builds it from scratch in the new conventions. No line-by-line translation, because direct ports smuggle in patterns that fight the framework. With that groundwork, moving a page stopped being a project and became a command. We ran it again and again — in parallel sessions, in git worktrees, in scheduled Claude Code routines that opened migration pull requests on their own. One of us had six sessions running at once. On our best day we merged twenty-seven pull requests.

Here is how the root AGENTS.md frames the migration:

Excerpt of the repository's root AGENTS.md: Migrating from ClientConsole.

Notice who that's addressed to. "Stop and flag the choice" is an instruction to the thing writing the code.

The same thinking spread sideways. A Storybook MCP server lets designers and product managers ask Claude about our real components, so designs start from what exists. And every user-facing string lives in a central messages file — one place where "Next", "Next Page", and "Next page" collapse into a single phrase, for people and agents alike.


Our tool choices were AI choices

Here is the part we think generalizes: every library decision was also an AI decision.

AI coding tools are only as good as their context — and their deepest context is the public record they were trained on. React has been the dominant front-end library for a decade; in Stack Overflow's 2024 developer survey, 39.5% of developers were using React against 6.5% for Svelte — a six-fold difference. That footprint means richer documentation, more worked examples, more answered questions — and models that know not just the syntax but the idioms, the pitfalls, the taste.

React 39.5% versus Svelte 6.5% developer usage — Stack Overflow Developer Survey 2024.

The same logic ran through the whole stack. We chose shadcn/ui, which officially supports React, over the community-maintained Svelte fork we'd been using. We chose react-hook-form, TanStack Query, and other libraries backed by teams with deep documentation, because many Svelte equivalents are maintained by individual contributors — thinner docs, thinner training signal. And we valued React's API stability: Svelte's move from version 4 to 5 changed its reactivity model, and AI tools trained across both versions still confidently mix the two. React's patterns have held for years, which gives a model one clear mental picture instead of two blurred ones.

None of this is a verdict on Svelte. It's an observation about how AI-assisted teams should choose tools: documentation quality and breadth of adoption aren't nice-to-haves anymore. They are the substrate your agents think with.


What the agents missed

It would be dishonest to say the agents did it all. They missed plenty — feature gaps, half-ported behaviors, edge cases only a person who knew the product would catch. We had Claude audit the new app against the old for missing functionality, bug-bashed as a company, and spent real weeks on follow-up. Review stayed human, and it stayed serious.

And the first assembled version of the app was slow. Server rendering is not speed by itself. What made the difference is that Next.js gave us levers Svelte never had — Suspense boundaries, prefetching, parallel data fetches, per-request cache lifetimes — and because the codebase was strictly modular, moving things onto those levers was easy. The page that waited on one slow query got a boundary around it. The sidebar that blocked on an org fetch stopped blocking. Days of tuning, not weeks. That's the honest shape of the win: not fast by default, but fast because the architecture let us reach every lever quickly.


Faster for users, faster for shipping

The results, measured in production with real users. First paint: 3.5 seconds to 0.6 — about six times faster to load. Page-to-page navigation: 1.2 seconds to 0.5. Layout shift fell from 0.09 to 0.02, so the app is steadier as it loads. Frustrated clicks — the rage-clicking our monitoring counts when an interface stalls — dropped from 1.1 per session to 0.34.

6× faster to load, 2.5× faster to use, 4× steadier, 3.5× fewer frustrated clicks.

And shipping is faster in kind. Four weeks from kickoff to production; roughly seven hundred pull requests in six weeks; a small team, not all of them engineers — which may be the most telling detail. Days after launch, we stood up a second branded app on the same codebase. Launch night, the monitors stayed quiet.


What carries over

If you're planning a migration — or any greenfield build — the framework question matters less than this one: can an agent, dropped anywhere in your codebase, figure out what good looks like from where it's standing?

Plan the architecture until there are no open questions. Write the conventions down where the agents will find them. Choose tools by the depth of their public record. Then let the tools do what they're good at.

A line we heard at a conference recently stuck with us: build something developer-friendly, and it will be agent-friendly. Our month agrees, in both directions. Everything the agents needed — decisions made once, context beside the code, tools with deep public records — is what a new engineer needs on day one. Good developer experience is no longer a nicety. It's what your fastest contributors run on.

We didn't make the AI smarter. We made the codebase easier to understand. The agents — and the people — did their best work in it.

Related Articles