先日リニューアルした本サイトについて、使用技術や作業工程ごとに振り返ります。
今回はウェブサイトのフレームワークである Astro を取り上げます。

Astro 公式サイト

ここ最近は、ウェブサイトを実装するときの構成として、静的サイトジェネレータは Eleventy、テンプレートエンジンは Nunjucks という組み合わせを多く採用してきました。

今回も候補にはあったのですが、せっかくなので新たなフレームワークに触れてみたかったので、静的サイトに特化している Astro を選択しました。

そのほかの候補としては Next.js がありました。

Astro インテグレーション

見出し「Astro インテグレーション」

Astro では、拡張機能のことを「インテグレーション(Integrations)」と呼びますが、本サイトで導入しているインテグレーションは以下です。

  • @astrojs/mdx : Markdown を拡張できる MDX ファイルのサポート
  • @astrojs/sitemap : XML サイトマップ(sitemap.xml)の自動生成

React、Vue、Svelte、といった UI フレームワークは使用しておらず、必要最小限の構成にしています。

画像最適化のインテグレーションについては後述しますが、導入を見合わせました。

Next.js などの他のフレームワークでも主流ではありますが、コンポーネントを組み合わせる手法は、直感的で迷うことが少なく、適度に複雑性を分散することができるので、本サイトのような小規模なウェブサイトでも開発しやすかったです。

また、テンプレート部分に直接 HTML を書くことができるのもうれしいポイントです。

以下、Astro コンポーネントの例です。

Astro コンポーネントの例
---
// コンポーネントの読み込み
import Nav from '@components/global/Nav.astro';
import BlogList from '@components/blog/BlogList.astro';

// ブログ記事データの取得
const posts = await Astro.glob('./blog/*[.md, .mdx]');

// 変数の作成
const links = [
  {
    'label' : 'Services',
    'href'  : '/services/'
  },
  /* ... */
];
---

<!-- コンポーネントテンプレート(HTML) -->
<header class="header">
  <Nav links={links} />
</header>

<BlogList posts={posts} />

<!-- CSS -->
<style>
.header {
  display: grid;
  grid-template-columns: 1fr auto;
}
</style>

<!-- JS -->
<script>
const header = document.querySelector('.header');
</script>

なお、本サイトでは使用しておりませんが、React などの UI フレームワークを使った場合には、Astro アイランド(コンポーネントアイランド)の恩恵も感じられるかもしれません。

Eleventy の場合には、基本的に HTML のビルドに限定するので、CSS や JavaScript のビルドは自前で用意する必要があるのですが、Astro では最初からこれらの環境が整っているので、ビルド環境を構築する手間がはるかに少なくなりました。

また、Astro はビルドエンジンに Vite を採用しているということもあり、ビルド時間や変更が反映される時間も高速です。

src/pages 以下にファイルを置けば自動的にルーティングしてくれるため、個別にルーティングの設定をする必要はありません。

また [slug].astro[...page].astro のような動的ルーティングにも対応しています。

本サイトのブログのトップページではページ分割を実装しており、ディレクトリ構造は以下のとおりです。

src/pages/
└── blog/
    ├── [...page].astro  // ブログトップ
    └── renewal-2023.mdx // ブログ記事

この [...page].astro がブログのトップページなのですが、これを最初は [page].astro としていました。

しかし、この状態でページ分割を有効にすると、/blog/ にはアクセスできず、/blog/1/ /blog/2/ /blog/3/ と分割されてしまいました。

ファイル名を [...page].astro と、スプレッド構文の形にすることで、/blog/ /blog/2/ /blog/3/ と、望んでいたとおりに分割されるようになりました。

Astro では、コンポーネント内の CSS セレクタには固有の class が振られます。

例えば、以下のような Astro コンポーネントがあるとします。

Astro コンポーネント
---
---
<a href="/blog/" class="btn">ブログ</a>

<style>
.btn {
  padding: 10px;
}
a {
  text-decoration: none;
}
</style>

出力されるファイルでは、astro-37FXCHFA のような astro- 接頭辞が付いたユニークな class が割り振られ、CSS のセレクタにも追加されるため、他のコンポーネントで同じ名前の class を指定していても競合することがありません。

生成される HTML ファイル
<a href="/blog/" class="btn astro-37FXCHFA">ブログ</a>
生成される CSS ファイル
.btn:where(.astro-37FXCHFA) {
  padding: 10px;
}
a:where(.astro-37FXCHFA) {
  text-decoration: none;
}

そのため、BEM のような命名規則を用いた、CSS セレクタの管理から解放されます。

一方で、<script> </script> で生成した JS は、グローバルスコープになるので注意が必要です。
コンポーネントとして読み込まれた JS が、他のコンポーネントに影響を及ぼす可能性があります。

ブログコンテンツの構築

見出し「ブログコンテンツの構築」

Markdown、MDX の両方の形式をサポートしており、ページ分割や RSS 配信も可能なため、ブログコンテンツを持つウェブサイトとの相性はとても良いです。

公式サイトのチュートリアルが「Build your first Astro Blog(ブログを作る)」なので、まずはこのチュートリアルをひととおり試してみるとよいかもしれません。

コードのシンタックスハイライトは、デフォルトの Shiki をそのまま使用しています。

また、Markdown の拡張機能として、見出しの直前にアンカーリンク を表示させるために、rehype-autolink-headings を使用しています。

Astro のペインポイント

見出し「Astro のペインポイント」

Astro は総じてパフォーマンスが高く、柔軟性に富んだフレームワークという印象で、これからも積極的に使っていきたいと考えていますが、導入してみて不満に感じた点や、苦労した点も少しだけ見受けられました。

まず、画像最適化の方法が確立しておらず、不安定な点が挙げられます。

Astro では、画像最適化のためのインテグレーションとして @astrojs/image または Astro ImageTools が存在しますが、いずれも安定版ではなく相互に互換性がありません。

本サイトは画像の点数が少なく、SVG 形式が多いという理由もありますが、検討した結果、今回はこれらのインテグレーションの導入は見合わせました。

もし、画像最適化の優先度が高い場合には、割り切っていずれかのインテグレーションを使用するか、画像処理に特化した CDN を使うといった方法を検討することになりそうです。

なお、この 2 つのインテグレーションの比較については、エビスコムさんの以下の記事が詳しいです。

開発モードとビルド後の挙動の違い

見出し「開発モードとビルド後の挙動の違い」

開発モード( astro dev )と、ビルド後のプレビュー( astro build の後に astro preview )で、挙動が若干異なることがありました。

具体的には、Google Chrome にて CSS Transitions を指定していると、ページロード時に発火してしまう問題が、開発モードでは発生せずにビルド後のコードにおいてのみ発生しました。

そもそも生成されるコードは異なるので、新たな機能を追加したときには、ビルド後のプレビュー確認は必須だと感じました。

Astro は比較的新しいフレームワークなので、ウェブ上のリソースはそれほど多くありません。

そのため、問題に直面した時にピンポイントで解決策が見つからないときがあります。

そのようなときには、まずは公式ドキュメントに立ち返るのが基本ですが、Astro のドキュメントサイト自体も Astro で作られており、GitHub リポジトリが公開されているので、それらのソースコードからヒントが得られるかもしれません。

また、公式ドキュメントの英語版と日本語版で翻訳のタイムラグがあるので、英語版のドキュメントで新たな情報が見つかる場合があります。

サイトリニューアル公開直前のタイミングで Astro v2 がリリースされたので、その機能はほとんど試せていません。

特に Content Collections については、機能を理解したうえで、必要に応じてこのサイトでも適用していきたいです。

また、現時点ではデータはすべてローカルにありますが、Astro と Headless CMS との連携も試していきたいと考えています。