Astroの特性と、技術選定で考えること
先日、第1回の Astro Meetup JP が開催され、勢いを増す Astro ですが、
まだ比較的新しい環境という点から、適性が掴めず検討中といった状況もあるかと思います。
この記事では、利点やプロジェクトに対する適正とデッドロックになり得るケースについて、特徴と紐づけながら書いていきます。
SPAとAstroの関係性
Astro はしばしば、SPAフレームワークと比較して特徴の可否で語られる事もありますが、
重要な点は、どちらも取って代わるものではなくあくまで選択肢であるという事です。
React / Vue は、2016年頃からは急速に普及した印象ですが、当時は、性質そのものより、従来のインタラクティブな振る舞いの実装におけるペインを解消する用途に着目される事も一定あったように思います。
しかし同時に、MPAの特性で成立するプロジェクトにおいても、表現を目的として全体をSPAで設計するケースも見られるようになりました。
特に、Next.js / Nuxt 以前は、chunk分割の最適化が十分でなく、バンドルサイズが肥大化している事例も見られました。また、同時期には、パフォーマンスやアーキテクチャの観点から、静的ベースの構成に対して部分的にVueやReactを差し込む、といったアプローチも取られていました。
現在の Astro は Server-First のコンセプトをベースに、SSG / SSR / SPA の議論よりも、UIをアプリケーションではなくコンテンツとして扱う立場に集中した結果、上記のような、アプリケーションを主体としない性質のプロジェクトで生じるオーバーヘッドを解決する選択肢となっています。
Astro の利点を逆引きする
Astro は、コンテンツ駆動型のWebフレームワークとして設計、最適化されており、
Content Collections を用いた構成や、JAMStack との親和性が高いといった特徴があります。
一方、それらがどのように差別化されており、何が嬉しいのか、その内容についても記載します。
導入がラクである理由
Astro の公式ドキュメントでは、"Easy to use" として、あらゆるWeb開発者にとって親しみやすい設計を掲げています。
Astro’s goal is to be accessible to every web developer. Astro was designed to feel familiar and approachable regardless of skill level or past experience with web development.
これはSFC関連の要素に限らず、CSSやJavaScriptのバンドル処理においても同様です。
従来、静的サイト環境を構築する場合、環境やスケールを意識せず、コマンド1つで、すぐに開発可能、といった柔軟な環境の汎用化は簡単ではありませんでした。
少なくともプロダクション、ステージング、ローカルに対する分岐や、
パスのalias、プリプロセッサの設定といった多数の処理に対する設計が必要で、これらに伴い、多くのdevDependenciesや環境用のscriptsを必要とした状況があったと認識しています。
こうした背景もあり、運用においてパッケージの互換性・脆弱性等に対するメンテナンスコストが肥大化しやすいという課題もありました。
Astro はViteの上に構築されており、上記のような構成や、バンドル処理の複雑さを意識する必要がほとんどありません。
具体的に、以下のような機能がデフォルトで提供されており、簡単なコンフィグで調整や拡張が可能です。
- CSS Modules、Scoped CSS、Sass / Less のサポート
- コード分割
- TypeScript のトランスパイル
- 画像アセットの最適化
このような特徴から属人性も低減され、普段はフロントエンドに関わりが薄いWeb開発者にとっても親しみやすい環境となっている印象です。
なぜ剥がしやすいか
Astro のロックインの少なさは、Astro Componentの設計に起因しています。
*.astro ファイルは、HTMLのみのテンプレートコンポーネントとして、クライアントサイドのランタイムを持ちません。
そのうえで、Islands Architectureによって UIライブラリ / UIフレームワーク(React/Vue/Svelte等)の SFCをそのままインポートして利用できるため、Astro 固有の記法に依存する範囲が限定されます。
結果として、コンポーネントに関して完全なロックインになり得る要素がなく、技術スタックの変更や部分的な移行が容易であり、特定のUIライブラリへの依存も最小限に抑えられます。
この点は、前述の「静的ベースの構成に対して部分的に Vue や React を差し込むアプローチ」の1つの解となっている印象です。
なぜパフォーマンスに効くか
Astro はデフォルトで Zero JavaScript という設計思想があります。
これは、JSを使わないことが目的ではなく、パフォーマンスの観点から不要ならば送らない(含めない)という思想が近いと思います。
この思想を実現しているのがPartial Hydrationの仕組みです。
ページ全体をhydrateせず、必要なコンポーネントだけをhydrateする方式で、Astro ではこれをIslands Architectureとして実装しています。
ハイドレーション戦略は、以下の client ディレクティブで制御します。
| 指定 | hydrateタイミング |
|---|---|
client:load |
ページ読み込みと同時に |
client:idle |
初期読み込みが終わり、ブラウザが待機状態になったとき |
client:visible |
表示領域に入ったとき |
client:media |
CSS メディアクエリの条件が満たされたとき |
client:only |
ページ読み込み時、すぐに (SSR せずクライアント専用) |
Astro におけるパフォーマンス施策は、ビルドの最適化をはじめ複数の要素がありますが、中でも、何を、いつhydrateするかを開発者が明示的に選択する設計は、他のフレームワークとの大きな差別化要因と言えます。
選定の指針
こうした特徴を踏まえて、どのような性質のプロジェクトに向いているのか、個人的な視点で書きます。
判断の軸
-
アプリケーションである必要がない場合
- コンテンツ駆動型で、インタラクションが部分的であるサイト
-
「できる」よりも「適している」で判断する
- 実装可能であることと、最適な選択であることは別問題
この記事で考慮から除外している事
- 別レイヤーとの兼ね合いや、ビルドに要する時間
- UIライブラリに更新性を持たせる設計
- 既存プロジェクトからの環境移行コスト
性質による適正
| 性質 | 特徴 | 具体例 | 適性 |
|---|---|---|---|
| CMS統合サイト | JAMstackライクな構成、ビルド時データ取得 (部分的な CSR も可) |
HeadlessCMS + SSG コーポレート、オウンドメディア、ドキュメント、ブログ、ポートフォオ等 |
✅️ |
| 部分的インタラクション | アイランド単位で完結するUI | タブ、フォーム、アコーディオン等 | ✅️ |
| グローバルな状態管理や、コンポーネント間の状態共有 | store の管理や Island 間の状態同期 | グローバルヘッダー <-> ページコンテンツ | 👀 要検討 |
| ページ跨ぎアニメーション | MPA特性による制約 | シームレスな画面遷移 (View Transitions APIで一部対応可) |
👀 要検討 |
| 管理画面・ダッシュボード | 複雑な状態管理、頻繁な更新 | SaaS管理画面、リアルタイムダッシュボード | ❌️ 不向き |
| WebSocket常時接続 | リアルタイム性が主要要件 | チャットアプリ、コラボレーションツール | ❌️ 不向き |
中間成果物の生成用途について
ここまでの内容とは前提が異なりますが、Astro の設計思想から逸脱する前提のもと、
ビルド済みの成果物をパージ・分解し、他のシステムから再利用するニーズも依然として存在しています。
そのような場合、Astro のScoped CSSによるhashの付与や、chunk分割、manifestによる動的なファイル参照が課題となることがあります。
こうしたケースでは、使用上の不確実性は高まりますが、パフォーマンスや環境機能への影響を把握し続ける前提で以下のようなアプローチにより対応することも可能です。
- Scopedを避けたCSS設計
- Client Directivesの使用制限
- chunk分割の抑制
Scopedを避けたCSS設計
成果物の再利用性を保つため、Astro のScoped CSSを回避する必要があります。
主な対応として、CSS Modulesや、*.astro内での<style>の使用を避け、静的なclass指定や命名規則を前提としたCSS設計とビルドを行います。
Client Directivesの使用制限
*.astroで、Client Directivesを使用することで、Island ごとに hydration用のJS chunkが生成されます。
中間成果物として再利用する前提では、これらのchunkが構成を複雑にする事もあるため、clientディレクティブの使用を制限し、Islandを生成しない構成とします。
chunk分割の抑制
Astro はViteを通じて、ページやコンポーネント単位で JS / CSSを自動的に分割して出力します。
環境の制約によっては、ファイルの分割や動的なパス参照が扱いにくい事もあるため、出力を単一の JS / CSSファイルに集約し、アセットの出力先を固定する構成とします。
vite: {
...
build: {
cssCodeSplit: false,
assetsInlineLimit: 0,
manifest: 'path/to/js/manifest.json',
rollupOptions: {
output: {
entryFileNames: 'path/to/js/index.js',
assetFileNames: assetInfo => {
const ext = assetInfo.names?.[0]?.split('.').pop();
const map = {
css: 'css/style.css',
ttf: 'fonts/[name][extname]',
woff: 'fonts/[name][extname]',
woff2: 'fonts/[name][extname]',
png: 'images/[name][extname]',
jpg: 'images/[name][extname]',
svg: 'images/[name][extname]',
};
return map[ext] || '[name][extname]';
}
}
}
}
}
まとめ
個人的には、フロントエンド環境において多様なフレームワークやライブラリには、それぞれ適した領域があると考えています。
Astro は、そうした領域での柔軟性が高く、積極的に採用したいフレームワークですね。
以下は、標準的な使い方を前提に、ウェブプロジェクト向けの汎用的な範疇で、
Svelte、linter、test、scaffolding tool、Husky などをセットアップした boilerplate です。
もしよろしければ、ご参考までに。
それでは、
Good luck out there, astronaut ! 🚀
Discussion