2026年10月25日、AI coding agentにaron.twをNext.jsからSvelteKitへ移してもらい、公開後にさらにAstroへ移行した。読者から見たサイトはほぼ同じで、検索、カテゴリ、言語切り替えも残っている。一方、検索ページの外部JavaScriptは155.7 KBから15.7 KBになった。
残したい機能を削らず、ブラウザがダウンロードして実行するコードを大きく減らせた。これが今回いちばん分かりやすい成果だった。
記事を読むことが中心のサイトで、実行モデルを変えると何が省け、何が残るのかを見てみたかった。当時の規模は5言語、記事の言語版35本、約60の静的ページ。Cloudflare Pagesに配置し、Pagefind検索、カテゴリ絞り込み、コピーボタン、テーマと言語のメニュー、ホームのポートレートスライダーを備えていた。
これらは個別に扱える操作だった。検索条件を変えても記事ページのメニューを更新する必要はなく、次の記事へ移るときに編集中のフォームを引き継ぐこともない。Astroを試せる条件がそろっていた。
既存の動作検証も引き継いだ。AstroとSvelteKitの静的・操作中画面210組を比較し、ピクセル差はゼロ。約223件のE2E受け入れ仕様は維持し、フレームワークの内部実装に依存する一部だけを調整した。機能が残ったかどうかを、性能の数値とは別に確かめられた。
HTMLが届いた後も、ブラウザは何をしているか
三つとも静的配置でHTMLを返す。違うのは、その後の処理だ。
Next.jsはビルド時にServer Componentsを実行し、HTMLとクライアント遷移用のpayloadを用意する。Client Componentsにはhydrationが必要になる。SvelteKitもページをhydrateし、サイト内の遷移をclient routerが引き継ぐ。こうした仕組みは、ブラウザ側で状態や操作を継続するためにある。Next.jsの静的出力、SvelteKitのページ設定
このサイトのAstro版は記事をビルド時に処理し、メニューや絞り込みを通常のTypeScriptに任せた。ブラウザでReactやSvelteがページを引き継ぐことはなく、次のHTML文書を読み込んで移動する。
検索ページは三版とも同じPagefindを含むため、機能を削って転送量を減らした比較にはならない。
検索ページの外部 JS 転送量
同じ検索機能でも Astro の外部 JS は少なく、HTML 内のスクリプトは別に数えます。
- Next.js155.7
- SvelteKit60.6
- Astro15.7
データ表を開く
| フレームワーク | KB(brotli) |
|---|---|
| Next.js | 155.7 |
| SvelteKit | 60.6 |
| Astro | 15.7 |
ローカルの Apple M3 Max、幅 390 px、CPU 4 倍スローダウン。Pagefind を含む最初のサンプルの外部 JS response body サイズで、時間の中央値ではありません。HTML 内のスクリプトは除外。KB = 1,024 bytes。
計測データの抜粋と方法一般の閲覧ページでは、Astroの外部JSは今回およそ0.9–3.2 KBだった。検索は別に数えている。また、小さなスクリプトの一部はHTMLに埋め込まれる。外部ファイルが小さいからといって、JavaScriptがないわけではない。
実行する仕事も減った。幅390 px、CPUを4倍に減速したChromiumで、各ページ5回の中央値を取ると次のようになった。
| 各ページの中央値の範囲 | Next.js | SvelteKit | Astro |
|---|---|---|---|
| Script実行時間 | 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 |
範囲はページ間の差で、一つのページの誤差区間ではない。heapはJavaScriptのメモリだけを表す。このブログでは、転送、実行、保持するメモリを減らせたことに意味がある。
ただしLighthouseのスコアはほとんど変わらなかった。別途行ったLighthouse 13.5.0のモバイルシミュレーションでは、各ページ3回の中央値でFCPが約3–12秒。三版に共通する11個の同期フォントstylesheetが、最初の描画を妨げていた。
次に文字を早く表示するなら、フォントに手を入れる。フレームワークを変えて省けた仕事と、残ったボトルネックが見えた。同じ問題に答えるために、もう一度移行する必要はない。
操作部分は、別の書き方で残る
SvelteKitへの移行では、二つの具体的な作業があった。
記事がHTMLにあるのにhydration payloadにも重複していたため、transport hookと独自の出力処理を組み合わせて、初回の重複を除いた。クライアント遷移用のデータは残している。既存の設定一つで済んだ処理ではない。
また、head内の約18個の高優先度modulepreloadがフォントと帯域を取り合った。当時の低速4GシミュレーションではFCPが0.6–0.9秒遅くなり、fetchpriority="low"で元の水準に戻った。bundleの大きさだけでなく、読み込む順番も効く。
Astroではそのhydration状態を再構成しないため、記事payloadの重複処理は不要になった。それでも検索やカテゴリ絞り込みは実装する必要がある。
記事カードを<template>に描画しておき、条件が変わるとスクリプトで該当カードを複製し、一覧、ページ番号、URLを更新する。ダイアログ、メニュー、ポートレートスライダーもそれぞれスクリプトを持つ。移行完了時点では8個のTypeScriptモジュール、860行だった。
移行記録には、コンポーネントのスクリプトが<template>内へ出力され、自動で動かなかった問題も残っている。当時はメニューのスクリプトを外側で読み込むようにしてイベントを接続した。hydrationがなくても、どこでスクリプトを実行するかは考える必要がある。
現在は各操作を独立して理解できるので、このコードを持つことを受け入れている。検索に編集機能や共有状態が増え、複数の領域が連動し始めたら、純粋なTypeScriptにこだわってDOMを手作業で同期し続けるつもりはない。必要な場所にはSvelteやReactのislandを置ける。
cold buildは短縮、warm buildでは差が縮む
ブラウザ以外では、cold buildが5.81秒から2.55秒に短くなった。
ローカルの cold build 時間
ローカルで交互に計測した結果、Astro の cold build は元の Next.js 実装より約 56% 短くなりました。
- Next.js5.81
- SvelteKit3.88
- Astro2.55
データ表を開く
| フレームワーク | 秒 |
|---|---|
| Next.js | 5.81 |
| SvelteKit | 3.88 |
| Astro | 2.55 |
Apple M3 Max、交互に実行した各 3 回の中央値。フレームワークと出力キャッシュを削除し、共通 OG 画像キャッシュは保持。コンテンツ処理とPagefindを含みます。warm buildは続く本文で比較します。
計測データの抜粋と方法計測にはコンテンツ処理とPagefindも含む。フレームワークのコンパイルだけの時間ではない。続けてビルドするとNext.jsの永続キャッシュが効き、warm buildはNext.js 2.98秒、SvelteKit 4.07秒、Astro 2.63秒だった。coldでの改善を、あらゆる条件で二倍速いという話には広げられない。
コードやパッケージの数でも順位をつけたくない。自分で持つ処理とフレームワークに任せる処理が異なり、行数から工数は出せない。860行を維持する価値があるかは、今後の変更を通して分かってくる。
ページ遷移の数字は、解釈を訂正する
ローカルで記事一覧から本文へ移った時間は、次の通りだった。
別の本番記録にはSvelteKit 373 ms、Astro 768 msという各3回の中央値がある。ただし取得したデプロイ時期が違い、CDNやネットワークの状態も、待機したライフサイクルも異なる。Playwrightのlocator.click()は起動した遷移を待つ。文書全体の読み込みとclient routerによる更新を、同じ終点として比較することはできない。
元の記録では約50件のリソースイベントを、大量の304検証と解釈した。この点は訂正が必要だ。requestfinishedにはbrowser cacheから取得したリソースも含まれる。各応答とキャッシュの取得元がなければ、イベント数からネットワーク往復の回数は分からない。
2026年10月25日に保存した本番の確認では、HTMLはmax-age=0, must-revalidate、フォントと抽出した画像は4時間のfreshness、hash付き/_astro/のCSS・JSは1年間のimmutableだった。4時間以内でまだfreshなフォントは、must-revalidateがあるという理由だけで毎回ネットワーク検証されない。この指示が制約するのは期限切れ後の再利用だ。HTTPキャッシュ仕様
文書全体を移動すればHTMLの解析とDOMの構築は再び行う。それでもCSS、フォント、画像はブラウザのキャッシュを使える。CDNのhitは別で、応答が利用者に近くなる一方、ブラウザからの要求自体は発生する。この区別なしに遅れの原因は決められない。
このサイトではhover prefetchも有効にしている。Astro 7.4.2は対応環境で<link rel="prefetch">により次のHTMLを先に取るが、次ページの全リソースや操作が準備済みになるわけではない。当時の300 ms hoverテストで処理時間が改善しなかったことは、その試行の結果であり、prefetch全般が無意味という結論ではない。
次に比較するなら、冷えた/温まったbrowser cacheを固定し、先読み完了を確認し、同じ可視コンテンツの表示時点を測る。JSとビルドの改善は既存データで分かる。遷移は別途、条件をそろえて確かめたい。
このブログでは、Astroを続ける
読むことが中心で、操作を局所的に分けられる現在のサイトには、ビルド時にページを完成させ、必要なコントロールだけブラウザに任せる構成が合っている。
ページ間で多くの操作状態を保ったり、複数領域を一緒に更新したりするなら、Svelteのリアクティブなコンポーネントやクライアント遷移には具体的な役割がある。SvelteKitもcsr=falseでhydrationとrouterを止められるが、Svelteに依存していた操作は別の形で用意する必要がある。
既存のNext.jsなら、client boundaryやルートごとの分割を先に確認する選択もある。今回はServer Actionsやstreamingを測っておらず、移行が元の実装の改善より割安だとも証明していない。AIと既存テストが今回の実験を可能にしたが、すべてのチームに同じ投資を勧めるものではない。
aron.twでは、Astroを残し、小さな操作部分を維持し、文字の表示を遅らせているフォントを直す。フレームワークの選択はいったんここまでにして、サイトの次の仕事へ進みたい。
