On October 25, 2026, I had an AI coding agent migrate aron.tw from Next.js to SvelteKit, then to Astro after the SvelteKit version went live. Readers saw essentially the same site, with search, categories, and language switching intact. External JavaScript on the search page fell from 155.7 KB to 15.7 KB.
That was the clearest gain: the browser downloaded and executed much less code without losing features I wanted to keep.
I wanted to see what a different execution model could remove from a site built mainly for reading—and what work it would leave behind. At the time, the site had five languages, 35 article language versions, and roughly 60 static pages on Cloudflare Pages. Interactions included Pagefind search, category filters, copy buttons, theme and language menus, and a portrait slider on the homepage.
Those interactions could be handled independently. Changing a search filter did not need to update an article's menu. Moving to the next article did not require preserving a half-edited form. That made Astro a reasonable experiment.
Existing behavioral checks carried through the migration. Comparing 210 static and interactive screenshots between Astro and SvelteKit found zero pixel differences. Roughly 223 E2E acceptance cases were retained, with a few adjusted where they depended on framework internals. These records let me check feature preservation separately from performance.
The HTML has arrived. What is the browser still doing?
All three versions were statically deployed and delivered HTML. The difference came afterward.
Next.js ran Server Components at build time and prepared both HTML and the payload for client navigation. Client Components needed hydration. SvelteKit also hydrated the page and handed internal navigation to its client router. Those mechanisms support continuing component state and navigation in the browser. Next.js static exports, SvelteKit page options
This site's Astro implementation finished article rendering at build time and used ordinary TypeScript for menus and filters. No React or Svelte runtime took over the page. Navigation loaded the next HTML document.
The search page makes a useful comparison because every version included the same Pagefind functionality.
External JS transferred on the search page
Astro sends less external JS for the same search feature; scripts inside HTML are counted separately.
- Next.js155.7
- SvelteKit60.6
- Astro15.7
Expand data table
| Framework | KB (brotli) |
|---|---|
| Next.js | 155.7 |
| SvelteKit | 60.6 |
| Astro | 15.7 |
Local Apple M3 Max, 390 px mobile viewport, 4× CPU slowdown. Includes Pagefind. External JS response bodies from the first sample, not a timing median; excludes inline HTML scripts. KB = 1,024 bytes.
Measurement extract and methodsAstro's ordinary reading pages used about 0.9–3.2 KB of external JS in this run, excluding search. Some small scripts were embedded in HTML, however. A small external file total does not mean no JavaScript.
Browser execution also fell. Chromium used a 390 px mobile viewport and 4× CPU slowdown, taking the median of five runs per page:
| Range of page medians | Next.js | SvelteKit | Astro |
|---|---|---|---|
| Script execution | 47–88 ms | 14–35 ms | 4–14 ms |
| JS heap | 3.1–4.2 MB | 1.6–2.6 MB | 0.8–0.9 MB |
These ranges span different pages, rather than error intervals for one page. Heap describes JavaScript memory only. Less code to transfer, execute, and retain was a useful improvement for this blog.
Lighthouse scores barely changed. A separate Lighthouse 13.5.0 mobile simulation, using three-run medians per page, still put FCP at roughly 3–12 seconds. The 11 synchronous font stylesheets shared by all three versions remained on the first-paint path.
To get text on screen earlier, I would work on those fonts next. The migration removed some work and made the remaining bottleneck quite concrete. Another framework switch would not answer that problem.
The interactions still needed an implementation
The SvelteKit migration left two instructive cases.
First, article content was present in the HTML and repeated in the hydration payload. A transport hook plus custom output processing removed that initial duplication while preserving the data needed for client navigation. This was extra work in this project, rather than a single built-in switch.
Second, roughly 18 high-priority modulepreload entries in the head competed with fonts. In the slow-4G simulation at the time, they added 0.6–0.9 seconds to FCP. Setting fetchpriority="low" restored the earlier level. Smaller bundles still need a sensible loading order.
Astro did not need that hydration state, so the duplicate article payload stopped being a problem. Search and category filtering still had to be written.
Article cards were rendered into a <template>. When filters changed, a script cloned matching cards, rebuilt the list, and updated pagination and the URL. Dialogs, menus, and the portrait slider had their own scripts. At migration completion, these interactions occupied eight TypeScript modules and 860 lines.
The migration record also includes a script-placement problem: a component script was emitted inside template content, where it did not run automatically. Moving the menu script outside the template connected the events. That historical issue is a useful reminder to check where scripts execute, even without hydration.
I accept this code because the interactions are currently understandable in isolation. If search grows to include editing, coupled panels, or substantial shared state, I would reconsider manual DOM synchronization. Astro can host a Svelte or React island where that becomes useful.
Cold builds improved; warm builds tell a different story
Outside the browser, cold build time fell from 5.81 to 2.55 seconds.
Local cold build time
In the interleaved local comparison, Astro’s cold build took about 56% less time than the original Next.js implementation.
- Next.js5.81
- SvelteKit3.88
- Astro2.55
Expand data table
| Framework | s |
|---|---|
| Next.js | 5.81 |
| SvelteKit | 3.88 |
| Astro | 2.55 |
Apple M3 Max, interleaved comparison, median of three runs each. Framework and output caches cleared; shared OG image cache retained. Includes content processing and Pagefind. Warm-build results follow in the text.
Measurement extract and methodsThese timings include content processing and Pagefind, not just framework compilation. On a subsequent build, Next.js's persistent cache narrowed the gap: warm builds took 2.98 seconds for Next.js, 4.07 for SvelteKit, and 2.63 for Astro. The cold-build improvement does not imply a twofold advantage under every build condition.
I would not rank these implementations by package or line counts either. They assign different work to the framework and to local code. Whether maintaining those 860 lines is worthwhile will become clearer through later changes; line counts cannot tell us the hours in advance.
Correcting the navigation interpretation
The navigation records are easier to misread than the JS totals. Locally, clicking from the blog listing into an article produced these durations:
A separate production record showed 373 ms for SvelteKit and 768 ms for Astro, each a median of three runs. Those samples came from different deployment periods, with different CDN and network conditions. They also waited on different lifecycles. Playwright's locator.click() waits for navigation it initiates; a complete document load and a client-router update do not necessarily have equivalent endpoints.
The original record also interpreted around 50 resource events as numerous 304 revalidations. That interpretation needs correcting. requestfinished includes resources retrieved from the browser cache. Without individual responses and cache origins, the event count does not reveal the number of network round trips.
The production inspection saved on October 25, 2026 found max-age=0, must-revalidate for HTML, four-hour freshness for fonts and sampled images, and one-year immutable caching for hashed /_astro/ CSS and JS. A font that is still fresh within those four hours does not require a network validation merely because must-revalidate is present. That directive governs reuse after staleness. HTTP caching specification
A full-document navigation does parse HTML and construct a DOM again. CSS, fonts, and images may still come from the browser cache. A CDN hit is different: the response is closer to the user, but the browser still makes a request. These cases need separating before assigning a cause to a delay.
The site also enables hover prefetch. Astro 7.4.2 uses <link rel="prefetch"> to fetch the next HTML document when supported. That does not prepare every resource and interaction on the destination page. The recorded test with 300 ms of hover showed no workflow-time improvement; it does not establish that prefetch is useless.
For another navigation comparison, I would fix cold and warm browser-cache conditions, check whether prefetch finished, and measure the same visible-content endpoint. The existing data already establishes JS and build improvements. Navigation deserves its own properly matched experiment.
I would keep Astro for this blog
This site is mainly for reading, with interactions that can be separated. Building the pages ahead of time and giving the browser only the controls it needs matches what I currently want to maintain.
I would make a different comparison for a project with substantial state carried between pages or several panels that update together. Svelte's reactive components and client navigation have concrete uses there. SvelteKit can also disable hydration and its router with csr=false, though interactions previously implemented in Svelte must then be handled another way.
For an existing Next.js project, inspecting client boundaries and route splitting may be a better first step. This site did not test Server Actions or streaming, and the migration does not prove that changing frameworks is more economical than improving the original implementation. AI and existing tests made this experiment feasible for me; that does not make the same investment worthwhile for every team.
For aron.tw, the decision is clear enough: keep Astro, maintain the small interactions, and address the fonts still delaying the text. The framework decision can rest. There is other work to do on the site.
