🏝️

Astro(Island Architecture)とNext.jsのパフォーマンス比較

2023/03/18に公開

概要

Island ArchitectureをベースにしたAstroというフレームワークがどういうユースケースで採用するとよさそうかを調べていく中で、AstroとNext.jsとでパフォーマンスの比較を行ってみました。

Astroのドキュメントにも、どういう時に採用するといいかは書かれていますが、実際に検証してみることで本当にそうなのかというところが理解深まった気がします。

補足

Astroを採用するとよさそうなケース

Astroの公式ドキュメントに下記のように記載されています。

Astroは、コンテンツが豊富なウェブサイトを構築するために設計されています。
// 中略
これに対して、最近のほとんどのWebフレームワークは、Webアプリケーションを構築するために設計されています。
// 中略
これはAstroを理解する上でもっとも重要な違いの1つです。Astroはコンテンツにフォーカスするという独自の立場からトレードオフを行い、アプリケーションにフォーカスしたWebフレームワークでは実装する意味がないような比類ないパフォーマンス機能を提供します。
-- https://docs.astro.build/ja/concepts/why-astro/#コンテンツ重視

要するに、下記の選択を取ると良いパフォーマンスを発揮できるよということだと思います。

  • 動的なコンテンツが多いWebアプリケーション(Twitterなど)
    • -> Next.jsなど
  • 静的なコンテンツが多く、かつ部分的に動的なコンテンツを組み込みたい(zennなど)
    • -> Astro

パフォーマンスの比較

下記の2パターンのページをAstroとNext.jsで作成し、配信されるコンテンツの合計サイズ、ページロード時のパフォーマンスを計測して比較しました。

  • パターン1: 動的なコンテンツ(Counter)と1000件の静的なコンテンツを表示するページ
    • Astroが得意としているようなページ
    • 静的なコンテンツが主。部分的に動的コンテンツがある
    • 動的なコンテンツ: よくあるようなCounter
    • 動的なコンテンツ(Counter)と1000件の静的なコンテンツを表示するページ
  • パターン2: 1000件の動的なコンテンツを表示するページ
    • Astroが得意としていないようなページ
    • 動的なコンテンツ: 項目ごとに削除ボタンを配置し、ボタンにクリックイベントを定義(clickされたlog出力するだけ)
    • Astroのコードでは、1000件のIslandがあることになります
    • 1000件の動的なコンテンツを表示するページ

検証コードは下記にあげています。

結論

Astroのドキュメント通りの結果になりました。
静的なコンテンツが多い場合はAstroを採用すると、より良いパフォーマンスが出せそうです。

前提

比較していく上で、下記の前提で実施しています。

  • それぞれproduction buildしてSSRでローカルから配信
  • それぞれgzipを無効化
  • それぞれ実装したページコンテンツ自体の差異がないように同じReact Componentを利用
  • Next.jsのversion: 13.2.4
  • Astroのversion: 2.1.3

比較結果: パターン1(動的なコンテンツ Counterと1000件の静的なコンテンツを表示するページ)

配信されるコンテンツの合計サイズ数は下記のように、Astroが小さくなっています。

Astro Next.js
サイズ 253kB 545kB
結果 astro-pattern1-1000-network nextjs-pattern1-1000-network

Astroは動的コンテンツ部分のみのhydrationが必要なのに対し、Next.jsはページ全体のhydrationが必要になるため、ソースサイズが増えることは納得できます。
静的なコンテンツの件数を増減させ、その時の配信サイズの変化を見ることで、hydrationのボリューム分サイズが増えることに対しより納得感が得られました。

Astro Next.js
サイズ(100件) 158kB 293kB
サイズ(1000件) 253kB(+95kB) 545kB(+252kB)

実際に配信されたソースを確認すると、SSRされたコードと、hydration用のコードが増加していたため、Astroに比べてNext.jsはhydrationのボリューム分配信するサイズが増えそうです。

また、ページロードのスクリプト実行時間もNext.jsの方が長くなっています。
おそらくhydarationする部分が多いためだと思われます。

Astro Next.js
スクリプト実行時間 64ms 109ms
結果 astro-pattern1-1000-performance nextjs-pattern1-1000-performance

ちなみにlighthouseの結果は次のとおりになりました。

Astro Next.js
結果 astro-pattern1-1000-lighthouse nextjs-pattern1-1000-lighthouse

比較結果: パターン2(1000件の動的なコンテンツを表示するページ)

配信されるコンテンツの合計サイズ数は下記のように、Next.jsが小さくなっています。

Astro Next.js
サイズ 722kB 502kB
結果 astro-pattern2-1000-network nextjs-pattern2-1000-network

ページロードのスクリプト実行時間は明らかにAstroの方が長くなっています。
※Astroのコードでは1000件のIslandがあることになります。

Astro Next.js
スクリプト実行時間 3592ms 102ms
結果 astro-pattern2-1000-performance nextjs-pattern2-1000-performance

このように、Astroのドキュメントにも記載しているとおり、Islandが多い場合はAstroは厳しそうです。
ただし、Astro側ではIslandごとにhydrateタイミングを制御できます。
client:visible を指定すると、コンポーネントがビューポートに入るとhydrateされるため、大きく改善します。 参考

astro-pattern2-1000-performance-visible

まとめ

Astroのドキュメントに記載しているとおり、静的なコンテンツが主で、部分的に動的なコンテンツを組み込みたい場合はAstroを選択するとよさそうに感じました。

  • 静的なコンテンツが主の場合、hydration用のコードを動的コンテンツ部分のみに抑えることができるため、配信するサイズとページロード時のスクリプト処理時間を小さく抑えることができそう。
  • 動的なコンテンツが主の場合(Islandの数が多くなる場合)、ページロード時のスクリプト処理時間が大きくなる。client directiveの活用でそこは小さく抑えられそうだが、あえてAstroを選択する理由はパフォーマンスの面ではなさそう。

参考

Discussion