最近、Astroというフレームワークについて耳にする機会が増えました。
Astroは2022年4月にベータ版がリリースされ、約一年でGitHubでのスター数は29.7kということで非常に勢いがあります。
https://github.com/withastro/astro
参考までに、Reactの有名なSPAフレームワークであるnext.jsのスター数は105k、
VueのSPAフレームワークであるNuxtは44.9kです。
https://github.com/vercel/next.js/
実際にAstroを使ってみたところ、もっと早く知っておけばよかった、、と思うほど、しっくりとくるフレームワークでした。「JSを残さないJSベースのフレームワーク」というのが衝撃的です。シンプルで使いやすく、個人的なプロジェクトでは今後どんどん採用したいなと思いました。
それでは、詳しく紹介していきます。
Astroの特徴
パフォーマンス(速度)がとにかく早い
Astroはとにかくページの読み込みが高速です。(詳しくは後述します)
詳細なレポートはこちら
https://astro.build/blog/2023-web-framework-performance-report/
コンテンツリッチなサイトに特化したフレームワーク
Astroは閲覧がメインになるWebサイトの開発が得意です。
- Webメディア
- ブログ
- ポートフォリオ
- マーケティング用のページやECサイトなど
ただ、複雑なWebアプリケーションを作成するにはあまり向いていません。
- ログイン機能が必要な管理ページのあるサイト
- SNS
- Todoリストアプリ
- ネイティブアプリのようなサービス
会員機能などが必要なWebアプリケーションを作成するならNext.jsを使う方が良さそうです。
有名どころのフレームワークでコンポーネントが作れる
React、Preact、Svelte、Vueなどの有名なフロントエンドフレームワークをサポートしています。
例えば、ヘッダーはReactを使い、カルセールはVueを使い、フッターでLitを使う、ことが可能です。
(昔に作ったVueのコンポーネントを使いまわしたい!という時に便利そうですね)
また、TypeScriptにも対応しています。
Astroが速いのはなぜ?
先ほど見たように、Astroはページを開いてから表示されるまでの速度がかなり速いです。(特に初回のローディング)
その理由としては、以下が挙げられます。
- SSGを採用しているから
- ビルド後のHTMLにJavaScriptが含まれないから
SSG(Static Site Generation)を採用しているから
SSGとは「Static Site Generation」(静的サイト生成)の略で、HTMLを事前に描画して生成するアーキテクチャのことです。
Laravelなどの馴染みのあるアプリケーションフレームワークでは、以下の流れでページが描画されます。
- ブラウザでURLを開くと、サーバーにリクエストが送られる
- サーバー側からDBにアクセスしてデータを取得
- サーバー側でHTMLを生成する
- HTMLをブラウザに返す
- ブラウザはHTMLを受け取ってユーザーに表示する
SSGでは、事前に生成しておいたHTMLを設置しておくことで、2,3の処理を飛ばし、高速にHTMLを返すことができます。
ビルド後のHTMLにJavaScriptが含まれないから
AstroはHTMLを生成する際に、JavaScriptをファイル内に残しません。
ビルド時にJavaScriptを実行してHTMLに値を描画した後、使用したJavaScriptをすべて取り除きます。
そのため、不要なファイルの読み込みや実行にかかる待ち時間が非常に少ないです。
ReactやVueなどのフレームワークでは、jsファイルが読み込まれるまでの数秒間ページが真っ白になってしまう、という現象が発生していました。
そのため、コンポーネント思考のreactの考えに共感しつつも、静的コンテンツがベースになるサイトではなかなかそれらを採用できないジレンマがありました。
Astroなら、コンポーネントを使ってUIを構築しつつ、読み込み時間の速さを保つことができます。
もちろんサイトの機能を使う上でJavaScriptが必要なケースもありますが、すべてのコンポーネントにJavaScriptが関与する必要はないはずです。
基本的には取り除いて、必要な部分だけJavaScriptを残す(これをAstroでは、Partial Hydrationと呼んでいます)のがAstroの考え方です。
Astroの環境構築
Astroはシンプルに使えることを念頭に開発されているようで、環境構築もとても簡単です。
プロジェクト作成
npm create astro@latest
上記を実行します。
色々と設定をどうするか、対話形式で聞かれますが、今回はすべて初期値で進めます。
$ npm create astro@latest
╭─────╮ Houston:
│ ◠ ◡ ◠ Let's make the web weird!
╰─────╯
astro v2.3.0 Launch sequence initiated.
dir Where should we create your new project?
./exotic-ephemera
tmpl How would you like to start your new project?
Include sample files
✔ Template copied
deps Install dependencies?
Yes
✔ Dependencies installed
ts Do you plan to write TypeScript?
Yes
use How strict should TypeScript be?
Strict
✔ TypeScript customized
git Initialize a new git repository?
Yes
✔ Git initialized
next Liftoff confirmed. Explore your project!
Enter your project directory using cd ./exotic-ephemera
Run npm run dev to start the dev server. CTRL+C to stop.
Add frameworks like react or tailwind using astro add.
Stuck? Join us at https://astro.build/chat
╭─────╮ Houston:
│ ◠ ◡ ◠ Good luck out there, astronaut! 🚀
╰─────╯
Astroのローカル開発サーバーを立ち上げる
npm run dev
上記のコマンドでローカルの開発サーバーが立ち上がります。
http://localhost:3000/で作成したプロジェクトが確認できます。
(ちなみに、AstroではNode.jsのバージョンが16.12.0必須です。未インストールの場合には、Node.jsをインストールしましょう。)
デフォルトのディレクトリ構造
主にsrcディレクトリの中にメインの処理を書いていきます。
Astroの基本的な書き方/構文
.astroの拡張子を使用する
基本的には、.astro拡張子を使ってAstroのコードを書いていきます。
コードフェンス(—-)内のコードは静的HTMLに変換される
Astro独自の書き方のようです。
コードフェンス(---
)内にコードを書くと、ビルド時に結果がHTMLに直接描画され、JavaScriptのコードは最終的に取り除かれます。
実際に例を見るとわかりやすいと思います。
JSXのような書き方
Astroでは、通常のHTML、CSS、JavaScriptが書けるほか、ReactのJSXようにしてコンポーネントを記述できます。
---
const name = "Astro";
---
<div>
<h1>Hello {name}!</h1>
</div>
上記のastroファイルは最終的に以下のHTMLに変換されます。
<div>
<h1>Hello Astro!</h1>
</div>
JavaScriptのコードは取り除かれてただのHTMLになります。
クラス名を変数で指定する
---
const name = "Astro";
---
<h1 class={name}>クラス名も変数で指定できます</h1>
JSでHTMLを生成する
---
const items = ["犬", "猫", "鳥"];
---
<ul>
{items.map((item) => (
<li>{item}</li>
))}
</ul>
JSXのように配列から動的にリストタグの作成ができます。
---
const visible = true;
---
{visible && <p>表示されます</p>}
{visible ? <p>表示されます</p> : <p>こっちは表示されません!</p>
このように演算子を使ってboolean型の変数を元に表示の条件分岐も可能です。
フラグメント
Astroでは、複数の要素をまとめてJavaScriptから生成する場合には、以下のように書く必要があります。
---
const items = ["Dog", "Cat", "Platypus"];
---
<ul>
{items.map((item) => (
<>
<li>Red {item}</li>
<li>Blue {item}</li>
<li>Green {item}</li>
</>
))}
</ul>
また、HTMLを丸ごと出力する際には以下のように書きます。
---
const htmlString = '<p>Raw HTML content</p>';
---
<Fragment set:html={htmlString} />
JSXと異なり、classNameではなくclassが使えます
JSXでは、className=と指定する必要がありましたが、Astroではclass=でクラス名が指定可能です。また、camelCaseを使う必要はなく、通常のHTMLと同じkebab-caseで指定可能です。
{/* JSX */}
<div className="box" dataValue="3" />
{/* Astro */}
<div class="box" data-value="3" />
Astroコンポーネントの書き方
コンポーネントを作成するには、まずコンポーネント用のファイルを追加します。
今回は、Card.astro
というファイルを作成して、カードコンポーネントを作ってみましょう。
コンポーネントのprops
コンポーネントはプロパティ(関数の引数ようなもの)をpropsという値で受け取ることができます。このあたりはReactのJSXの書き方にもとてもよく似ています。
受け取りたい変数は、以下のように定義します。
---
const { href, title, body } = Astro.props;
---
Astro.propsのオプジェクトにプロパティが入ってきますので、コードフェンスの中で分割代入します。
TypeScriptでpropsの型定義も可能
TypeScriptを有効にした場合は、propsの型を定義することもできます。TypeScriptを使わない場合はこの行は不要です。
---
export interface Props {
title: string;
body: string;
href: string;
}
const { href, title, body } = Astro.props;
---
変数
そして受け取ったプロパティ名を{href}
のように中括弧で囲んでHTMLテンプレートに書きます。
---
const { href, title, body } = Astro.props;
---
<li class="link-card">
<a href={href}>
<h2>
{title}
</h2>
<p>
{body}
</p>
</a>
</li>
コンポーネントのスタイル
コンポーネントに付与したいスタイルはそのままHTMLの下の方にタグを書いて、追加できます。
---
const { href, title, body } = Astro.props;
---
<li class="link-card">
<a href={href}>
<h2>
{title}
</h2>
<p>
{body}
</p>
</a>
</li>
<style>
.link-card {
list-style: none;
padding: 1.4rem;
background-color: white;
border-radius: 0.6rem;
box-shadow: 0 4px 6px -1px #cacaca;
}
.link-card > a {
text-decoration: none;
line-height: 1.4;
color: #111;
}
p {
margin-top: 0.5rem;
margin-bottom: 0;
color: #444;
}
h2 {
margin: 0;
font-size: 1.25rem;
}
</style>
また、追加されたstyleは自動的にスコープされて、他のコンポーネントに影響しません。
<style>
h1 {
color: red;
}
.text {
color: blue;
}
</style>
例えば、上記のCSSは自動的に以下にコンパイルされます。
<style>
h1:where(.astro-HHNQFKH6) {
color: red;
}
.text:where(.astro-HHNQFKH6) {
color: blue;
}
</style>
そのため、CSSのセレクタを長くする必要がなく、クラス名の使用も最低限でよくなります。
コンポーネントのimport
作成したコンポーネントは、以下のように他のコンポーネントやページでimportして使えます。
---
import Card from '../components/Card.astro';
---
<main>
<ul>
<Card
href="https://docs.astro.build/"
title="Documentation"
body="Learn how Astro works and explore the official API docs."
/>
<Card
href="https://astro.build/integrations/"
title="Integrations"
body="Supercharge your project with new frameworks and libraries."
/>
</ul>
</main>
スロット
レイアウト型のコンポーネントでは、<slot/>
を使うことで、子要素を中に含めることができます。
---
import Header from './Header.astro';
import Logo from './Logo.astro';
import Footer from './Footer.astro';
const { title } = Astro.props
---
<div id="content-wrapper">
<Header />
<Logo />
<h1>{title}</h1>
<slot /> {/* 子要素はここに入ります */}
<Footer />
</div>
子要素は、コンポーネントのタグに囲まれた中に書きます。
<コンポーネント名>子要素</コンポーネント名>
---
import Layout from '../components/Layout.astro';
---
<Layout title="フレッドのページ">
<h2>フレッドについて</h2>
<p>ここでは、フレッドについて紹介します。</p>
</Layout>
ReactやVueのコンポーネントもimportして使うことができる
---
import MyReactComponent from '../components/MyReactComponent.jsx';
import MySvelteComponent from '../components/MySvelteComponent.svelte';
import MyVueComponent from '../components/MyVueComponent.vue';
---
<div>
<MySvelteComponent />
<MyReactComponent />
<MyVueComponent />
</div>
ReactやVueで作成したコンポーネントもコードフェンスの中でimportすると、使えるようになります。
クライアントサイドでJavaScriptの実行が必要な場合は、client:loadを使う
Astroでは、ビルド時に基本的に全てのJavaScriptが取り除かれるためパフォーマンスが高いです。
でも、クライアントサイドでJavaScriptを使いたい時もありますよね(例えば、スライダーカルーセルの実装やメニューボタンの実装など)
そんな時は、client:loadという特別なディレクティブを使います
---
import InteractiveButton from '../components/InteractiveButton.jsx';
import InteractiveCounter from '../components/InteractiveCounter.jsx';
import InteractiveModal from "../components/InteractiveModal.svelte"
---
{/* このコンポーネントのJavaScriptはページ読み込み時にインポートが開始されます */}
<InteractiveButton client:load />
{/* このコンポーネントのJavaScriptはユーザーがスクロールしてコンポーネントがページ内に表示されるまでクライアントに送信さません */}
<InteractiveCounter client:visible />
スクロールして、コンポーネントが表示されるまで読み込まれない「client:visible」というディレクティブなども用意されています。
- client:がついている場合
- HTML + CSS + JavaScriptがブラウザ上で描画される
- client:がついていない場合
- HTML + CSSのみしか描画されない
AstroはMPAフレームワーク
AstroはMPA(マルチページアプリケーション)型のフレームワークです。対になる概念として、SPAフレームワークというものが存在します。二つのフレームワークの違いとメリットデメリットを紹介します。
SPA(シングルページアプリケーション)とは?
最近のフロントエンド界隈では、SPAフレームワークが流行っていますね。
SPAとは「シングルページアプリケーション」の略で、単一のページを起点として、JavaScriptがHTMLの描画をまるっと行うようなアプリケーションのことです。
画面の遷移を行う際には、同じHTML上でページ上のdomのみを書き換えるためネイティブアプリのようにスムーズに動作します。
例えば、Next.js、Nuxt、Gatsbyなどのフレームワークが有名です。
MPA(マルチページアプリケーション)とは?
マルチページアプリケーションとは、複数のHTMLで構成されるアプリケーションのことです。
昔からあるサイトはほとんどこの形式だと思います。
例えば、Laravel、Phalcon、WordPress、Ruby on Railsなどの多くのWebアプリケーションフレームワークはMPAに該当します。
新しいページに遷移する際には、サーバーから新しい別のHTMLを受け取り、ブラウザで表示します。
[SPAのメリット]複雑なUIを持つWebアプリケーションが得意
SPAは単一のJavaScriptアプリケーションがWebサイト全体のページ遷移やイベントの動作を管理するため、複雑なUIを構築したり、UXを向上させることが得意です。
例えば、Gmail、Googleカレンダー、Notion、Slack、Twitterなどは全てSPAですね。
最近のSaaS系のサービスなどもSPAで作られていることが多いです。
[SPAのデメリット]SPAは初回の読み込みが遅い
SPAはページ遷移時にサーバー側ではなくローカルでHTMLを描画するため、基本的にはページの遷移がサクサクできます。
ただし、欠点として初回のページのローディングが遅いです。
なぜなら、SPAではHTMLのレンダリングをJavaScriptアプリケーションが担当するため、レスポンスを受け取ってから初回のコンテンツが読み込まれるまでに時間がかかるからです。
■SPAで、初回読み込み時にコンテンツが描画されるまでの流れ
- ブラウザがサーバーからレスポンスを受け取る
- ブラウザ側でJavaScript各種ファイルのダウンロード、読み込み
- JavaScriptアプリケーションが実行される
- 初回ページで表示されるコンテンツの取得を行う
- JavaScriptがコンテンツを描画する
[MPAのメリット]MPAは最初の読み込みが速い
一方で、MPAは初回の読み込みがSPAよりも速いです。
SPAでは、ページの部品がバラバラに届き、それらをブラウザ側で組み立てる必要があるのに対して、MPAでは完成された1枚のページが直接ブラウザに届けられるためです。
ただし、2回目以降のページ遷移に関してはMPAはSPAより遅くなります。
SPA | MPA | |
---|---|---|
1回目の読み込み | × 遅い | ○ 速い |
2回目以降の読み込み | ○ 速い (1回目より速い) | × 遅い(1回目とほぼ変わらない) |
[MPAのデメリット]ユーザー体験はSPAに劣る
MPAのデメリットとしては、前述のSPAの裏返しになりますが、複雑なUIを持ちサイト全体を通して状態管理をする必要のあるようなWebアプリケーションは得意ではありません。
MPAではページ遷移の度にHTMLを再度取得する必要があるためローディングに時間がかかります。SPAやネイティブアプリのサクサクな動きに慣れたユーザーには、使いづらいサービスだと感じられてしまうかもしれません。
直帰率が高いページにはSPAは向いていない
そのため、直帰率が高く、ページを遷移しないユーザーが多いサイトではSPAではなく、MPAを使う方が良い場合が多いです。
特に、コンテンツ型のサイト(Webメディア、ブログ、マーケティング用サイト、ランディングページ、ECサイト)などは上記に該当することが多いでしょう。
ページ読み込みが速いほど、SEOの評価やサイト内でのコンバージョンレート(転換率)が高いことが知られていますので、初回のローディングが重要になるかどうかでSPAかMPAを使用するか、慎重に考える必要があります。
Astroを使ってみた感想
JavaScriptを使ってHTMLを生成したり、コンポーネントを使うことでメンテナンス性を保ちながらも、JavaScriptをHTMLに残さないことでページの読み込み速度をあげられる、というのがAstroの強みです。
学習コストも少なく直感的に使えるようになりました。
また、ReactやVueなどのコンポーネントをimportすることでさらに複雑なUIを作成することもできます。
最終的に作成されるファイルがシンプルなHTMLとCSSになるのも、シンプルでわかりやすいです。
ブログやオウンドメディア、LP、簡単なホームページなどの構築ではAstroをどんどん使ってみたいと思います。
関連記事
よかったら、こちらの記事も読んでみてください。
reactでポケモン風RPGゲームを作ってみよう!戦闘画面編
https://qiita.com/udayaan/items/38680c63ed034503eac0
Electron + Reactでデスクトップアプリを作ろう!
https://qiita.com/udayaan/items/2a7c8fd0771d4d995b69
Electronを使ってMacとWindowsで動くアプリを作ってみる
https://qiita.com/udayaan/items/dfb324bc6cadeb9a8f6f