Astroをフロントエンドフレームワークとして利用する
この記事は「BEMA Lab Advent Calendar 2024」の4日目の記事です。
はじめに
こんにちは、池本です!最近は主に技術支援に取り組んでいます。
静的サイト生成(SSG)やJamstack向けフレームワークとしてよく使われるAstroですが、シングルページアプリケーション(SPA)やサーバーサイドレンダリング(SSR)のためのフロントエンドフレームワークとしても十分使えるポテンシャルがあります。
この記事では個人開発での経験を通して、フロントエンドフレームワークとしてのAstroの特徴を述べてみます。
Astroの基本思想
最初に、Astroの技術的な基本思想について紹介します。
サーバーファースト
公式ドキュメントのAstroを選ぶ理由によれば、Astroは技術的には「サーバーファースト」であり、言い換えると従来のサーバサイドフレームワークのように動くのが特徴です。
また、クライアントサイドレンダリング(CSR)ではなく、サーバーサイドレンダリング(SSR)を用いていると述べられています。
Astroは、ブラウザ上のクライアントサイドのレンダリングよりも、サーバーでのレンダリングを可能な限り活用します。 これは、従来のサーバーサイドフレームワーク(PHP、WordPress、Laravel、Ruby on Railsなど)が何十年も使ってきたアプローチと同じです。しかし、そのために2つ目のサーバーサイド言語を学ぶ必要はないのです。Astroでは、すべてがHTML、CSS、JavaScript(お好みでTypeScript)だけでいいのです。
このアプローチは、Next.js、SvelteKit、Nuxt、Remixなど、他のモダンなJavaScriptウェブフレームワークとは対照的です。これらのフレームワークは、ウェブサイト全体をクライアントサイドレンダリングすることを念頭に作成されており、サーバーサイドレンダリングは主にパフォーマンス上の懸念へ対処するために行われます。
このアプローチはシングルページアプリケーション(SPA: Single Page App)と呼ばれ、Astroのマルチページアプリケーション(MPA: Multi Page App)アプローチと対照的です。
静的サイト生成(SSG)とSSRの融合
Astroはサーバーサイドレンダリング(SSR)を活用しつつ、静的サイト生成(SSG)を実現する優れた仕組みを持っています。以下のプロセスが特徴的です。
具体的には、SSGモードで動いているAstroのコンポーネント中で fetch() を使用すると、1度だけデータフェッチが実行され、取得されたデータに基づいて静的なHTMLが生成されます。
Astroコンポーネントのすべてのデータは、コンポーネントがレンダリングされるときにフェッチされることを覚えておいてください。
デプロイされたAstroサイトは、ビルド時に一度だけデータをfetchします。開発環境では、コンポーネントの更新時にfetchされたデータが表示されます。クライアントサイドで何度もデータを再取得する必要がある場合は、Astroコンポーネントでフレームワークコンポーネントまたはクライアントサイドスクリプトを使用します。
この2つをまとめると、次のようになります。サーバサイドレンダリングとして動いて、初回のfetchした状態を静的サイトとしてビルドする、これがAstroの考え方です。
- SSGモード: サーバサイドレンダリング + 初回のデータfetch
- SSRモード: サーバサイドレンダリング + オンデマンドレンダリング
Astroアイランドによる柔軟な開発
Astroの特筆すべき機能の一つに「Astroアイランド」があります。これは静的なコンテンツの中に一部インタラクティブなコンポーネントを配置できる機能で、Jamstackのような、コンテンツメインで一部インタラクティブな機能が求められるサイトに適しています。
AstroアイランドではReact, Vue, Svelteなど様々なUIフレームワークに対応しています。さらに驚くことに、それらのUIフレームワークを混ぜて使うことも可能です。
自分もこのAstroアイランドを使った個人開発を行っています。その使用例について記載します。自分は最初、ReactとGraphQL APIのみでフロントエンドを構成していました。
その後、Astroを基盤とし、ReactとSolidJSを使った構成に変更しました。次の図はその構成を示しています。このようにAstroを基盤にしてReactとSolidJSを混在させることで、機能ごとに適したUIフレームワークを選択できるようになりました。
Astro内でReactとSolidJSを併用する際のコードは次のようになります。 client:only を使って、使用するUIフレームワークを指定します。
<FooWidget client:only="solid-js" />
<BarWidget client:only="react" />
Astroアイランドを利用することで、異なるUIフレームワークのコンポーネントを簡単に併用し、実用的で柔軟なアプリケーションを構築できます。しかし、複数のUIフレームワークを用いることはできても、サイト全体で互いに異なるUIフレームワークを自由に使う機能ではないため、ReactからSolidJSのコンポーネントを呼び出すことはできません。
複数のUIフレームワークを混ぜる場合の注意点
Astroアイランドはトラブルなくスムーズに動作しますが、複数のUIフレームワークを組み合わせる場合、単一のフレームワークを使用する際には通常発生しないいくつかの注意点があります。
ディレクトリ構成の重要性
1つ目の注意点です。Astroのインテグレーションを複数導入する際は、パスの指定を正確に行う必要があります。Astroのドキュメント「複数のJSXフレームワークを一緒に使用する」にも説明がある通り、「このディレクトリはReact」「このディレクトリはSolidJS」といったディレクトリ構成を重複しないように明確に設定することが必要です。もし設定ミスでディレクトリが重なると、意味不明なエラーで泣きます(泣きました)。自分の場合はSolidJSをメイン、Reactをサブにしているので、次のように書いています。
export default defineConfig({
integrations: [
solidJs({
include: ["**/*"],
exclude: [
// ここにReactのディレクトリが入る
// 例: "src/widgets/react/**/*,
]
}),
react({
include: [
// 上のexcludeと同じディレクトリが入る
// 例: "src/widgets/react/**/*,
]
}),
});
異なるJSXを使うための設定
2つ目の注意点は、JSXのインポートを指定するためのjsxImportSourceの設定です。ReactとSolidJSの両方でJSXを使用しますが、それぞれの文法には微妙な違いがあります。たとえば、ReactではCSSのクラスを指定する際にclassNameを使用しますが、SolidJSではclassを使用します。現在どちらのJSXを使用しているかをAstroやIDEに認識させるために、tsconfig.jsonやコンポーネント側の設定が必要です。
tsconfig.jsonでは次のように、メインで使うjsxImportSourceを指定します。SolidJSを使う場合はこのように指定します。
{
"compilerOptions": {
"jsxImportSource": "solid-js"
}
}
サブで使用するJSXは次のように、ソースコードの先頭に記載します。
/** @jsxImportSource react */
CSSフレームワークの競合と共有
複数のUIフレームワークを併用する場合、必要なCSSフレームワークも複数になることがあります。似たようなCSSフレームワークを使用する場合には、設定における競合に注意が必要です。一方で、互換性があれば同じCSSフレームワークを共有することも可能です。自分は、Reactでshadcn/uiを、SolidJSでそのクローンであるshadcn-solidを利用していますが、Tailwind CSSの設定は共通です。
以上が、複数のUIフレームワークを混在させる場合の注意点です。一つのUIフレームワークだけを使用する場合には、このような注意点がありません。
Webアプリケーションとしての利用(SSRモード)
SSGモードで静的サイトを生成する場合、JavaScriptやHTMLのソースコードに認証なしでアクセスできます。コンテンツ中心のサイトではあまり問題になりませんが、Webアプリケーションとして利用する際には、一部のソースコードでも認証なしに見られては困る場合もあります。
そういうときにお手軽な解決案は、AstroをSSRモードにすることです。SSGと比べて毎回動的にHTMLを生成するためサーバの負担が大きくなりますが、コンテンツメインではなくWebアプリケーションとして利用するのであれば、SSRモードが適しているでしょう。
AstroでSSRモードにするためには大きく分けて4つの作業が必要です。
- アダプタの選択
- Astroの設定変更
- サーバ起動
- ページに認証機能を追加
SSR用アダプターの選択
SSRモードを利用するためには、静的ファイルの配信とは別にサーバを動かす環境と、そのためのSSR用アダプターが必要です。公式ではCloudflare, Netlify, Node.js, Vercelの4つのSSR用アダプターが提供されていますが、個人開発では最も汎用的に使えるNode.jsを選択しました。
Astroの設定変更
Astroの設定ファイル(astro.config.mjs)に次のように設定します。"standalone" は単独でサーバとして動かす場合の設定で、これ以外にExpressなどのHTTPサーバのミドルウェアとして動く "middleware" オプションがあります。実際に確認した訳ではないので詳細は記載しませんが、ExpressだけでなくHonoに対応している実績もあるようです。
export default defineConfig({
output: "server",
adapter: node({
mode: "standalone",
}),
});
サーバ起動
"standalone" モードの場合、公式ページにあるコマンドでサーバを起動します。
HOST=0.0.0.0 PORT=4321 node ./dist/server/entry.mjs
ページに認証機能を追加
最後に、認証が必要なページに次のように認証機能を追加します(認証機能の詳細は省略します)。認証に失敗した際はリダイレクトするように記載すると良いでしょう。
---
const cookies = Astro.cookies;
const isAuthenticated = ...; // ここでcookieの中身を確認
if (!isAuthenticated) {
return Astro.redirect(...); // 認証エラー時にリダイレクト
}
---
開発体験の良さと注意点
このAstroアイランドを使った開発は、開発体験としてはとても良いです。これまで書いたコンポーネントはそのまま利用でき、Astroは静的サイト生成(SSG)として使いやすいため、スムーズに移行できました。ただし、Astroはフロントエンドフレームワークとしては向いていない場面もあり、要件に応じてAstroが適しているどうかを判断する必要があります。
Astroは基本思想としてマルチページアプリケーション(MPA)として機能します。これによって、各ページは独立してレンダリングされ、ページ遷移のたびに新しいHTMLをサーバーから取得します。この手法はサーバの負担が少なく、初回表示速度において非常に有利ですが、ページ間遷移でのロードが負担になります。
Astroはビュートランジション機能を使うことで、シングルページアプリケーション(SPA)のような滑らかな画面遷移を実現することも可能ですが、ページ間での情報共有やグローバルな状態管理が多い場合、localStorageなどに保存する必要があります。
自分が作っている個人サイトでAstroを利用する際は、ページごとに独立した役割を持っており、異なる機能が必要なコンテンツで構成しています。また、状態管理はバックエンドで処理しているため今のところ問題点は発生していませんが、サイトの性質によってはMPAであることが足枷になる可能性があります。
Astroを活用するシナリオ
注意点はありつつも、Astroをフロントエンドフレームワークとして活用するシナリオをいくつか紹介します。
1つ目は静的サイトとして構築しているAstroからの移行。
静的サイトを構築するためにAstroは広く使われています。既にAstroを使用して静的サイトを構築している場合、そのサイトをよりインタラクティブにするために、UIフレームワークを採用することで、漸進的な移行が可能です。
2つ目はSPAとしての利用。
先ほど書いたように、Astroは基本的にMPAとして設計されているため、グローバルな状態管理が多い場合はlocalStorageなどに保存する必要があります。逆に言えば、グローバルな状態管理が少ないSPAを構築する場合にはAstroは向いています。
3つ目はUIフレームワークの乗り換え。
自分の場合ReactからSolidJSに乗り換えましたが、一旦Reactを使ったページをAstro + Reactで作り直してからReactをSolidJSに乗り換えることで、漸進的な乗り換えができました。あまり需要はないと思いますが、選択肢の1つとして考えておくといいかもしれません。
4つ目は学習用途。
自分はReactからSolidJSに乗り換えましたが、あえて少しReactを残しています。1つのサイトの中で複数のUIフレームワークが共用できるので、Reactのライブラリの方が良い場合は使おうかなと思っています。
おわりに
例はまだ少ないかもしれませんが、AstroとReactの組み合わせは十分に本番環境での運用に適していると考えています。コンテンツメインだが動きも欲しいなど、プラスアルファが必要とされたときの選択肢として頭の隅に入れておくと良いと思います。
今回は取り上げませんでしたが、AstroにはServer Islandsという新機能が追加される予定です。これはコンテンツ主導の静的なサイトで一部の動的な部分を遅延ロードできる機能です。Astroでは現在、ページ単位でSSGモードかSSRモードかを選ぶことも可能ですが、Server Islandsが加わることで、柔軟性がより向上するので楽しみです。
この記事を書いた人
関連記事
- React Redux: 毎回新しい参照を返す select...
Daisuke Yamamura
- 画像のカラー抽出を頑張った話。
狩野真毅
- 3Dの第一歩、Blenderを活用してひとつ先の未来へ
スーパーたけちゃんマン