My Migrate to Svelte 5 site started as a side-by-side reference for developers wanting to convert to Svelte 5, from React, Vue, or Angular. It maps concepts across frameworks: "your useState is Svelte's $state," "your useEffect is $effect," and so on — about 300 entries covering syntax, architecture, and ecosystem.
That's useful, for a human reading the site. But I kept running into a different scenario: working in a SvelteKit codebase with Claude Code and wanting to ask "go check what's new in Svelte and see what applies here." Because that kind of quick synthesis is what AI Agents are good at. The data was all on the site, but there was no machine-friendly way to query it for actionable patterns.
So I added two things to the site: a structured patterns feed and a browsable patterns page. This post covers the thinking and the implementation.
The Problem with Static Migration Guides
A migration guide answers "how do I do X in Svelte?" That's a pull interaction — the developer has a question and looks up the answer. Useful, but limited to that style of questioning.
What I wanted was a push interaction: "here are the 34 things Svelte shipped since 5.0 that you should know about, with exact code patterns to search for in your repo." An AI coding assistant could fetch this, grep the codebase, and say "you're still using writable() stores in 12 files — here's the $state class replacement." A bit of a pain for a human dev, but short work for an AI.
The key insight: the migration guide already had the "from" and "to" for each pattern. It just needed to be structured as queryable data instead of prose.
Why a JSON Feed, Not a Custom Format
My first attempt was a /llms-whatsnew.txt endpoint with bespoke SEARCH FOR / REPLACE WITH sections. It worked, but it was a made-up format pretending to be part of a standard. The llms.txt convention defines only two files — llms.txt and llms-full.txt. Adding llms-whatsnew.txt was inventing a spec, and agents would not be able to figure that out unless you specify each time.
A JSON Feed 1.1 solved this properly:
- It's a real spec with existing tooling (feed readers, aggregators, automation)
- Extension fields (underscore-prefixed) carry custom data without breaking compatibility
- Any tool that understands JSON Feed can consume it; the
_svelte_patternextensions are bonus data for tools that know about them - One endpoint serves feed readers, AI agents, and automation equally
The decision came from asking a simple question: if someone discovers this endpoint without documentation, can they use it? With JSON Feed, yes — any feed reader picks it up. With a custom .txt format, no, not really.
The Patterns Data Structure
Each pattern in patterns.ts represents one actionable Svelte feature:
interface SveltePattern {
id: string;
title: string;
title_ja?: string;
since: string; // "svelte@5.29.0" or "sveltekit@2.55.0"
date_added: string;
category: 'syntax' | 'architecture' | 'ecosystem' | 'tooling';
frameworks: ('react' | 'vue' | 'angular' | 'svelte4' | 'all')[];
summary: string;
summary_ja?: string;
search_signatures: string[]; // grep-friendly patterns
replacement: string; // modern Svelte 5 code
release_url: string; // GitHub release page
docs: { label: string; url: string }[];
}
The search_signatures field is what makes this useful for automation. Each entry is a string you can literally grep for. An AI assistant reads these, runs them against your codebase, and finds where old patterns still live.
Here's what an attachments pattern looks like:
{
id: 'attachments',
title: 'Attachments Replace use: Actions',
since: 'svelte@5.29.0',
search_signatures: [
'use:action', 'use:tooltip', 'use:clickOutside',
"from 'svelte/action'", 'Action<'
],
replacement: `{@attach (element) => {
element.focus();
return () => { /* cleanup */ };
}}`,
release_url: 'https://github.com/sveltejs/svelte/releases/tag/svelte%405.29.0',
docs: [{ label: 'Attachments', url: 'https://svelte.dev/docs/svelte/@attach' }],
}
The release_url links directly to the GitHub release page for the version that introduced this feature. Every pattern is traceable, then, to its exact release.
Two Feeds, Two Audiences
I ended up with two separate JSON Feed endpoints serving different purposes:
/feeds/changelog.json — what changed on the site itself, e.g. new mappings added, content updates, bug fixes. For subscribers who want to know when the reference itself is updated.
/feeds/patterns.json — the actionable patterns feed. Each item is a Svelte feature with search signatures, replacement code, and a link to the GitHub release. Supports filtering:
/feeds/patterns.json?since=5.54 # Svelte 5.54+ patterns only
/feeds/patterns.json?category=syntax # Just syntax patterns
/feeds/patterns.json?framework=react # Patterns relevant to React migrants
/feeds/patterns.json?lang=ja # Japanese summaries
The separation here is important, because the audiences are different. Someone subscribing to the changelog wants to know "did this reference get updated?" Someone (or something) querying the patterns feed wants to know "what Svelte features should I adopt?"
The Feed Extension
JSON Feed 1.1 supports extension fields prefixed with an underscore. Standard feed readers ignore them; tools that understand the schema use them. The _svelte_pattern extension carries the structured data:
{
"version": "https://jsonfeed.org/version/1.1",
"title": "Svelte 5 Migration Patterns",
"_svelte_patterns_meta": {
"total_patterns": 34,
"filtered_patterns": 34,
"usage_hint": "Each item._svelte_pattern.search_signatures contains grep-friendly strings..."
},
"items": [
{
"id": "https://svelte.cogley.jp/patterns/attachments",
"title": "Attachments Replace use: Actions",
"date_published": "2026-03-08T00:00:00Z",
"tags": ["syntax", "all"],
"_svelte_pattern": {
"id": "attachments",
"since": "svelte@5.29.0",
"category": "syntax",
"frameworks": ["all"],
"search_signatures": ["use:action", "use:tooltip", "use:clickOutside"],
"replacement": "{@attach (element) => { ... }}",
"notes": "use: still works but is legacy. Attachments work on components too.",
"release_url": "https://github.com/sveltejs/svelte/releases/tag/svelte%405.29.0",
"docs": [{ "label": "Attachments", "url": "https://svelte.dev/docs/svelte/@attach" }]
}
}
]
}
The _svelte_patterns_meta at the feed level includes a usage_hint that tells any AI agent reading the feed exactly how to use the search signatures. Self-documenting data.
The Patterns Page
The feed is machine-readable. For humans, there's /patterns — a filterable browser with dropdowns for version, category, and framework, plus free text search. Each card shows:
- Version badge and category/framework tags
- A bilingual summary
- Search signatures as copyable code badges
- The replacement code in a syntax-highlighted block
- Links to the GitHub release and Svelte documentation
It uses the same patterns.ts data file that the feed endpoint reads, so there's a single source of truth. The page is SSR'd on Cloudflare Workers — no client-side fetch needed.
The filters are reactive using Svelte 5's $derived.by(). Changing any dropdown or typing in the search box immediately narrows the visible patterns. The text search matches across titles, summaries, notes, code, and search signatures in both languages.
What's Covered
At the time of this post, there are 34 patterns spanning Svelte 5.0 through 5.55 and SvelteKit 2.10 through 2.55:
Core runes (5.0): $state, $derived, $effect, $props/$bindable, snippets, onclick handlers, .svelte.ts modules, $inspect, $state.raw/.snapshot
Incremental releases: error boundaries (5.3), {#each} without as (5.4), exportable snippets (5.5), MediaQuery class (5.7), Spring/Tween classes (5.8), reactive window values (5.11), ClassValue + object/array class syntax (5.16), $props.id() (5.20), writable $derived (5.25), attachments (5.29), snippet generics (5.30), fromAction() (5.32), getAbortSignal() (5.35), async components (5.36), createContext (5.50), TrustedHTML (5.52), parametric compiler options (5.54), motion type exports (5.55)
SvelteKit: init hook (2.10), transport hook (2.11), $app/state (2.12), async reroute (2.18), getRequestEvent() (2.20), remote functions (2.27), SSR error boundaries (2.54), type-narrowed params (2.55)
Each links to its GitHub release page, so you can trace the exact changelog entry, if and as needed.
Using It with an AI Assistant
The practical use case: you're in a SvelteKit codebase and want to modernize. Tell your AI assistant:
"Fetch https://svelte.cogley.jp/feeds/patterns.json and check what patterns from this feed apply to this repo."
The assistant fetches the feed, reads the search_signatures from each pattern, greps the codebase, and reports which old patterns it found with the modern replacements. No manual searching through docs.
For a more targeted audit:
"Fetch https://svelte.cogley.jp/feeds/patterns.json?since=5.29 and see if we're using any pre-5.29 patterns that have better alternatives now."
You can also filter by what you're migrating from:
"Fetch https://svelte.cogley.jp/feeds/patterns.json?framework=react and show me the patterns relevant to this React-to-Svelte migration."
The feed's _svelte_patterns_meta.usage_hint field tells the assistant exactly how to interpret the data, so it works even without prior knowledge of the feed's structure.
Real-World Results
I pointed Claude Code at the patterns feed across several production repositories and asked it to audit each one. Here's what happened.
Fixes Applied Immediately
| Repo | Before | Fix | Files |
|---|---|---|---|
| pub-cogley |
onMount/onDestroy lifecycle, manual AbortController, duplicated utils |
Converted 19 lifecycle calls to $effect across 4 apps, extracted shared utils, added cookie secure flag |
25 |
| pulse | Custom window.dispatchEvent('org-switched') pattern |
Replaced with $state-based reactive signal in .svelte.ts module |
7 |
| codex |
$app/stores imports ($page) |
Converted to $app/state with $derived()
|
2 |
| periodic |
$app/stores + writable() language store |
Converted to $app/state (9 files) + rewrote language store as .svelte.ts with $state rune (60 consumers) |
70 |
| courier |
$app/stores + writable() language store + unvalidated route params |
Converted to $app/state (3 files), rewrote language store (9 consumers), added param matchers for route type safety |
14 |
That's 118 files modernized across 5 SvelteKit codebases, but I also applied it to a non-svelte project, and a greenfield project that used the feed as a day-one adoption checklist.
What Was Already Modern
Every repo was already using core Svelte 5 patterns: $state, $derived, $effect, $props, snippets, onclick handlers, use:enhance. The feed correctly identified these as "no action needed" and focused on the gaps — mostly $app/stores → $app/state conversions and lingering writable() stores.
The Greenfield Case: Mame
One repo — mame, an internal learning platform started the same day — used the feed proactively rather than retroactively. Instead of finding legacy code to fix, it used the patterns feed as a kind of checklist for what to adopt from the start. The result: createContext<T>() for type-safe context (eliminated prop drilling across 18 files), $props.id() for SSR-safe form IDs, MediaQuery class for reactive breakpoints, and svelte/reactivity/window for reactive viewport values — all patterns that the existing repos had flagged as "future opportunities" but hadn't adopted yet.
The Nexus Case
One repo — nexus — is a pure Hono API worker with zero .svelte files. The feed audit correctly reported "N/A" for all Svelte patterns. It did find deprecated shared docs containing Svelte 4 examples that had been migrated to another repo, which were cleaned up.
Future Opportunities
Every repo got a tailored list of patterns that are available but not yet adopted — type-narrowed route params (SvelteKit 2.55), SSR error boundaries (2.54), createContext<T>() (5.50), getAbortSignal() (5.35). Not urgent, but now documented as backlog items. The "Not Applicable" column was equally useful — the feed correctly identified cases where patterns don't apply, like getAbortSignal() in repos where fetches are user-triggered button clicks rather than $effect blocks.
Implementation Notes
Feed Endpoints
The feed endpoints are SvelteKit server routes returning json() responses with CORS headers and 1-hour cache. Both are at /feeds/ — a dedicated namespace that won't conflict with page routes.
Version Filtering
The patterns feed supports version filtering via the since query parameter. The implementation uses semver comparison on the since field:
-
?since=5.54matches allsvelte@5.54.0+patterns but notsveltekit@patterns -
?since=kit@2.55matches allsveltekit@2.55.0+patterns but notsvelte@patterns
This lets you ask "what's new since version X?" for either package independently.
Feed Autodiscovery
Feed autodiscovery is handled by <link rel="alternate" type="application/feed+json"> tags in the SvelteKit layout. Any feed reader that visits any page on the site will discover both feeds automatically.
Bilingual Support
Both feeds support ?lang=ja to return Japanese titles, summaries, and notes where available. The patterns data is bilingual at the source level — every pattern has both title and title_ja, summary and summary_ja.
Layout and Navigation
The patterns page sits alongside the existing home (migration mappings), about, and changelog pages. Feed links appear in the global footer on every page, and the changelog page has a JSON Feed badge in its header. The layout includes theme and language toggles in the nav bar, accessible from every page.
How This Fits Together
The migration guide now has three machine-readable interfaces, each for a different use case:
| Interface | Audience | Best for |
|---|---|---|
/llms-full.txt |
LLMs reading the whole site | "Tell me about Svelte 5 migration" |
/feeds/patterns.json |
AI coding assistants | "Audit this codebase for modernization" |
| WebMCP tools | Browser-based AI agents | "What's the Svelte equivalent of useEffect?" |
The patterns feed fills the gap between "read everything" (llms-full.txt) and "answer one question" (WebMCP). It's the "here's what's actionable" layer — structured enough for automation, browsable enough for humans.
Related
This builds on the original migration guide article and the WebMCP tools implementation. The patterns feed complements WebMCP — WebMCP is for browser-based AI agents interacting with the page, while the JSON Feed is for CLI tools and coding assistants that can fetch URLs directly.
Originally published at cogley.jp
Rick Cogley is CEO of eSolia Inc., providing bilingual IT outsourcing and infrastructure services in Tokyo, Japan.
Top comments (0)