技術ブログをGatsbyからAstroに乗り換える

カバー

今月から、当技術ブログのビルドフレームワークをGatsbyからAstroへと乗り換えました。少しだけ見た目も変わったでしょうか。この記事では、移行の経緯とその方法、そして苦労した点などをご紹介します。

技術ブログの立ち上げそして移行へ

2019年の年末にそれは始まった

移行の話の前に、当技術ブログの立ち上げ時を簡単にふりかえります。雑記ですので、興味ない方は、「Astroとは」まで読み飛ばしてください。

2019年末、「技術ブログを立ち上げよう」との上司の号令のもと、ブログ構築の検討が始まりました。当時は、同業他社の半数近くが技術ブログの運用を実施していた時期でした。

私自身は、ふだんは業界動向調査や決算関連の業務などの傍ら、技術調査なども担当する技術者と非技術者の中間的な立ち位置で仕事をしています。当時採用し始めたばかりで慣れない「ユーザーストーリー」をたくさん設定しながら技術ブログのコンセプト作りと提供価値の検討をしました。また、記事執筆体制を作る難しさを感じつつ、様々な構築フレームワークを候補に調査を進めました。

見込む効果(社内提案資料より)

見込む効果(社内提案資料より)

当初は、WordPressのようなCMSも視野に入れつつ、セキュリティ面などの運用のことを考えると静的サイト生成(SSG:Static Site Generator)に分があると考え、Hugo、Hexo、Jekyllなどを候補にしていました。しかし、各フレームワークのベースとなる言語の理解が難しかったり、提供されているテーマが中国語で作られていて利用しにくい面もあったりするなどで、どれを採用するか悩んでいました。そんな中、ReactをベースにしたGatsbyが目に付きました。

ReactはVue.jsと並んで、一般的にもこなれてきた時期であり、結果的にSPA(Single Page Application)な構成のページを作るには最適な選択肢だったと思います。ただ、Reactの知識そのものはなかったため、結局のところそれをベースとするGatsbyではカスタマイズしきれなかった、というのが反省点です。

ノッてきたが壊れてきた

日常業務を行いながらの構築で半年以上の時間がかかってしまいましたが、何はともあれ、2020年7月にGatsbyでのブログ運用が始まりました。記事は、担当部門がMarkdownで執筆し、それを運営側でビルド、静的ファイル群をウェブサイト管理者に渡してアップする、という流れです。少しまどろっこしい手順ですし、ホスティングサービスのNetlifyやVercelを使う方法もありますが、自社サイト運用のワークフローをそのまま利用する形としました。

そして現在。約3年半が経過し、各担当部門でも執筆体制が整備され、毎月コンスタントに技術記事が発行できるようになりました。執筆部門内でもレビューの精度があがり、記事の品質はより高度で技術企業としての風格も出てきたのではないかと自負しています。もちろんアクセス数も徐々に増えてきて、一般の技術者の助力に少しはつながってきたのではないかと思っています。

このように、内容の濃い記事が多くたまってきた一方で、運営としてはこれらのコンテンツの存在感をもっと打ち出せるようなサイトにしていきたいという課題もあります。前述した通り、Reactの理解が進まないことが改善につながらない要因のひとつになっていました。

さらに悪いことに、ビルド環境のGatsbyが特定の環境でないと動作しないようになってきました。特定のテーマを使い、相性の観点からGatsby自体のバージョンをあげられなかったこと、それに伴い、Node.jsのバージョンが古いものでないと動作しなくなりつつあったことなどが原因で、ビルド自体が微妙な状況になってきました。執筆者側で記事のプレビューをする際にも確認がしづらく影響が出てきます。Gatsbyに本格的につっこんでいくか、乗り換えるかの決断が必要になっていました。

エラーが出る

Gatsbyでビルドするとエラーが・・・!

Astroが輝いて見えた

そんな折に、AstroというSSGフレームワークがすごい、という記事がちらほら目に入るようになってきました。様々なフレームワークが使える、最終的にスクリプトを使わない、レンダリングが早い、アイランドがすごい(何のこと?)、簡単に使える etc.。「ブログを作る」がチュートリアルになっているように、技術ブログを作るのに適したものに見えました。

近場にVue.jsの技術者が多くいたので、GatsbyのVue.js版ともいえるVuePressも乗り換え候補にしていましたが、結局Gatsbyと運用レベルが変わらないのと、機能面での不足を感じたことから不採用としました。半分非技術者の身としては、ReactベースのGatsbyはなかなか難しく、構造も複雑だったので、シンプルなAstroに惹かれていきます。

よし、Astroで行こう!

Astroとは

概要とアーキテクチャー

2022年8月に登場したAstroは、比較的新しいフレームワークです。ですが、ものすごい勢いでバージョンアップが重ねられ、機能追加も活発です。情報を追い始めた時のバージョンは2.xだったのが、移行を検証している間に3.xになり、こうして記事を書きながら確認すると、なんと4.0になっていました。

「Astroとは何であるか」は、たくさんの技術者さんが書いているので、ここでは、特徴を3つ挙げます。

スクリプトを極力排除

静的ページとして出力されるリソースには、クライアント上で動作するためのJavaScriptが含まれません。MPA(Multiple Page Application) であるため、コンテンツページはすべて、事前に個別のHTMLとしてレンダリングされ、静的なファイルを高速に表示します。ただし、動的な処理のためにJavaScriptを含めることももちろん可能です。

インテグレーション(多彩なフレームワークへの対応)

ページやコンポーネントをReact、Preact、Svelte、Vue.js、SolidJS、AlpineJS、Litといった様々なUIフレームワークで構築することができます。これは、既存のリソースを有効活用したり、好みのフレームワークでの構築を可能にしたりします。

アイランドアーキテクチャー

ヘッダー、サイドバー、フッターなどのUIコンポーネントを個別にレンダリングするアーキテクチャーです。各コンポーネントは、別々のフレームワークを使うこともでき、一つのページ内に複数のフレームワークで事前に生成したコンテンツを表示することができます。動的なコンテンツと静的なコンテンツの混在も可能です。

もちろん、他のSSGフレームワークと同様、Markdownをコンテンツとして利用可能、URLルーティング、テーマの利用、ページコンポーネントやレイアウトでのリソース共有、拡張機能の豊富さなど基本的なサイト構築フレームワークとしての機能は必要かつ十分に備えています。また、SSR(Server Side Rendering)での動作も可能です。

初心者にやさしいAstro

Astroを気に入ったのは、何よりも構造がシンプルであることでした。たとえば、コンポーネントを使った場合のhello worldページを表示する構成であれば、下のように pages ディレクトリーと components ディレクトリーに1つずつの最低限のファイルだけで十分です。

.
├── package.json
├── node_modules
└── src
    ├── pages
    │   └── index.astro
    └── components
        └── Head.astro

ファイルの中身も後述する通り、とてもシンプルで理解しやすいものになっています。

コンポーネントを組み合わせてサイトを作るのが初めての人でも理解しやすく、プログラミングをしたことのない人でもとっつきやすいものになっています。それでいながら、スクリプトを書いていくことで、より高度なことも行うことができ、先の説明通り、多彩なフレームワークも使えることから拡張性が高いフレームワークとなっています。

変えるところ・変えないところ

ここで、Gatsby版からAstro版へ乗り換えるにあたり、変えたこと・変えなかったことを挙げてみます。なお、デザインは極力踏襲しつつ、細かいところで改善を加えることにしました。

テーマは使用しない

GatsbyもAstroもテーマ機能があり、公式や有志が作成した優れたサイトデザインを使うことで、クールなサイトを容易に構築できます。

たくさんのテーマ

Astro公式サイトにはテーマがたくさん

Gatsby版では初期の導入を楽にするためテーマを利用していましたが、今回はデザインを踏襲することにしたため、できあいのテーマは使用しませんでした。Gatsbyでは、テーマは node_modules にインストールされ(=直接編集できない)、これをシャドウイングという上書きのしくみで src 上で修正する形でデザインの調整をしていました。これがややこしく、自由度が効きづらい要因となっていたのですが、Astro版ではTailwindで直接コンポーネントやページ内にスタイルを書くことで、ファイル構成は非常にシンプルなものとなりました。

なお、Tailwindは初めて使ったのですが、個人的には 「クラス名を指定せずに各所に読みにくいユーティリティクラスを埋めていくなんてCSSの概念に反してる!」 と思っていたものの、実際に使ってみるとコンポーネント単位で作る場合は重複もしないですし、簡易な表記で埋めていきながらデザインしていけるのは非常に便利だと思いました。

コンテンツデータの取得が事前に処理される

一言でいえば、MPAとSPAの違いです。Gatsbyは、コンテンツやパラメーターなどがすべてビルド時にクエリー言語のGraphQLのデータとして登録されます。そして、クライアントアクセスの際に、スクリプトでGraphQLに対してクエリーが発行され、ページが形成されます。システム構造としては、データベースを使ったコンテンツシステムをクライアントサイドで実行できる面白いしくみではあるのですが、理解すべき内容が多く、コンポーネントのソースもその分複雑になっていました。

AstroはシンプルなHTMLに少しだけロジックを組み合わせ、そのロジックの結果もビルド時に静的なHTMLに変換されるので、アクセスしたクライアント側の処理はシンプルなHTML表示だけと、とても簡単なものになります。

細かいカスタマイズ

その他、細かいところで変更を加えました。

  • Twitterが "X" に代わり、さあ共有アイコンを変えようと思い立つも、Gatsbyではできあいのコンポーネントを使っていて、Xの新アイコンをうまく表示できませんでした。技術ブログ界隈では必須とも言える「はてなブックマーク」への共有にも対応できずにいました。Astroでは独自に共有用のコンポーネントを作り、好きにアイコン・リンク先を設定可能となりました。
  • Gatsbyでは各記事のキーワードであるタグに日本語が使えず、またデザイン的にも目立ちませんでした。Astroでは各記事のサムネイルにもタグを表示させ、もちろん日本語も使えるようになりました。 サムネイルイメージ
  • ヘッダーをグラデーションにしました ;-)
    ※ 案外こういう修正が一番印象を変えるものです

変わらないように気を使う

一方で、変更しないように気を使ったところもあります。特にすでにある記事リソースをそのまま使えること・外部からのアクセス性が変わらないことには十分な注意が必要です。

  • URL:検索エンジンのインデックスに乗り、外部からのリンクもしていただけた3年間の積み重ねがふいにならないようにしなければいけません。記事へのリンクは、以前と変わらないようなルーティングとしました。といっても、Astroはファイル・ディレクトリー構成がそのままルーティングとなるので楽でした。
  • シンタックスハイライト:AstroではShikiがデフォルトになっており、スタイルテーマも良さそうなのですが、Gatsbyで利用していたPrismを踏襲して、これまでと極力フォーマッティングが変わらないようにしました。
  • 数式:記事の中には、下のような数式を扱うものがあります。TeXの記法をそのまま使えるMathJaxを利用していたので、そのまま同じライブラリーを使用しつつ、数式がない記事では無駄にライブラリーをロードしないよう、記事ごとに使用の有無を設定できるようにしました。 $$ \displaystyle \ket{\psi} = \frac{1}{\sqrt{2}}\ket{0} + \frac{1}{\sqrt{2}}\ket{1} $$

Astroで技術ブログを作る

インストールができない?

乗り換えにあたり、まずはAstroに触ってみます。Astroには自動CLI(インストーラー)が用意されており、下のコマンドだけで、必要なライブラリーやディレクトリー構成をインタラクティブに整備してくれます。

$ npm create astro@latest
Need to install the following packages:
create-astro@4.5.2
Ok to proceed? (y)


 astro  Launch sequence aborted.

      ▲  error Unable to connect to the internet.
npm ERR! code 1
npm ERR! path /home/alice/myastro
npm ERR! command failed
npm ERR! command sh -c create-astro

npm ERR! A complete log of this run can be found in: /home/alice/.npm/_logs/2024-01-22T04_57_41_166Z-debug-0.log

だめでした。 どうもこのインストーラーでは、giget というテンプレートダウンローダーを利用しており、これがプロキシーに対応していないようです。当社では、インターネット接続にはプロキシーを経由することが必須なので、社内からは利用できませんでした。

ちなみに、正しく動作すると下のようなかわいらしいキャラクターがインストールを手伝ってくれます。

╭─────╮  Houston:
│ ◠ ◡ ◠  Let's build something fast!
╰─────╯

Hello World

仕方ないので、手動で作っていきます。

$ mkdir myastro
$ cd myastro
$ npm init --yes
Wrote to /home/alice/myastro/package.json:

{
  "name": "myastro",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

$ npm install astro

added 443 packages, and audited 444 packages in 18s

181 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Astroのインストールはこれで完了です。次にページコンテンツを作ります。Astroのソースはすべて src ディレクトリーの下に作られ、そのうち基本となるページは src/pages ディレクトリーの中に作ります。

Astroでは、HTML形式のほか、独自のAstro形式、Markdown形式、MDX形式のファイルに対応しており、すべて静的なHTMLに変換して表示されます。

$ mkdir -p src/pages
$ vim src/pages/index.astro
src/pages/index.astro
---
const title = "Hello world";
---
<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>{ title }</title>
</head>
<body>
  <h1>{ title }</h1>
</body>
</html>

Astro形式は、--- でくくられたスクリプト部分と、2番目の --- 以降の HTML 本文の2段構成で、本文には export 処理や return 処理など、余計な式や構文は不要です。本文中には {} で囲うことで、変数やスクリプトを使うことができます。

では、ローカル環境で動作を確認できる開発モードで実行してみます。

$ npx astro dev
14:39:15 [types] Added src/env.d.ts type declarations
14:39:15 [vite] Port 4321 is in use, trying another one...

 astro  v4.0.4 ready in 66 ms

┃ Local    http://localhost:4321/
┃ Network  use --host to expose

14:39:15 watching for file changes...
14:39:16 [200] / 4ms

表示されたURLにウェブブラウザーでアクセスすると、期待通り「Hello World」が表示されました。

レイアウトとコンポーネントを粛々と作る

共通のレイアウトは、src/layouts ディレクトリーにページと同様のAstro形式で定義できます(特になければスクリプト部は省略できます)。レイアウトファイルの <slot /> に呼び出し側のコンテンツが挿入された状態で表示されます。

src/pages/mypage.astro
---
import PageLayout from "../layouts/PageLayout.astro";
---
<PageLayout>
  <p>私のページです</p>
</PageLayout>
src/layouts/PageLayout.astro
<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>Hello world</title>
</head>
<body>
  <h1>Hello world</h1>
  <slot /> <!-- ここに呼び出し側の中身(<p>私のページです</p>)が入る -->
</body>
</html>

レイアウトの構成

コンポーネントは、src/components ディレクトリーに設置し、呼び出し側のスクリプト部分でインポートしたうえで、本文部分でタグとして埋め込む形となります。たとえば、先程作った index.astro にヘッダーを埋め込む場合は次のようになります。

src/pages/index.astro
---
import PageHeader from "../components/PageHeader.astro";
const title = "Hello world";
---
<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>{ title }</title>
</head>
<body>
  <PageHeader />
  <h1>{ title }</h1>
</body>
</html>
src/components/PageHeader.astro
<a href="/">
  <header>
    <div class="logo">
      <img src="/images/alphalogo_mini.png" alt="会社ロゴ" />
    </div>
  </header>
</a>

コンポーネントの構成

このような感じで、レイアウトとコンポーネントをパーツごとに作っていきます。

ブログ記事はコンテンツの集まり

ブログは、ある型に沿った記事の集まりです。これをAstroでは「コンテンツコレクション」という機能で管理できます。コンテンツコレクションは、 src/content/XXXX(XXXXは任意で、たとえば blog など)ディレクトリーにファイルを置いておき、これらを一定のフォーマットでコンテンツ化できます。

早速記事ファイルを用意してみます。ファイル形式はMarkdownにしました。

.
└── src
    └─── content
        └── blog
            ├── article001.md
            ├── article002.md
            └── article003.md

ファイルの中身は簡単に下のようなものです。Markdown形式ではありますが、記事の上部にはAstro形式と同じく --- でくくった中にYAML形式のフロントマターを定義できます。

src/content/blog/article001.md
---
title: 記事1
---
### 記事1です

- 箇条書き1
- 箇条書き2
- 箇条書き3

こういった記事はどんどん増えてきますので、テンプレートとして定義したページから共通的に呼び出す形にします。

Astro形式のファイルを作り、ファイル名は [...slug].astro とします。これは、REST形式でのルーティングを実現するもので、ファイル内で getStaticPaths 関数を使うことで、各記事のファイルやディレクトリー名(この例では slug )がパラメーターとして取得できるようになります。

また、コンテンツコレクションは、 getCollection 関数を使って取得します。

src/pages/[...slug].astro
---
import PostLayout from "../layouts/PostLayout.astro";
import { type CollectionEntry, getCollection } from "astro:content";

export async function getStaticPaths() {
  const posts = await getCollection("blog");
  return posts.map((post) => ({
    params: { slug: post.slug },
    props: post,
  }));
}
type Props = CollectionEntry<"blog">;

const post = Astro.props;
const { Content } = await post.render();
---

<PostLayout {...post.data} slug={post.slug}>
  <Content />
</PostLayout>

この状態で、 http://localhost:4321/article001 にアクセスすると、Markdown形式のコンテンツがHTMLに自動的に変換され、PostLayoutのレイアウトに基づいてページ表示されました。

記事インデックスの表示

記事ページができたので、最後にインデックスページを作ります。これもコンテンツコレクションを取得して、そのタイトルとページURL(スラッグ)を使えばできそうです。

先程作った index.astro にリスト部分を追記しました。

src/pages/index.astro
---
import PageHeader from "../components/PageHeader.astro";
import { getCollection } from "astro:content";
const posts = (await getCollection("blog"))
const title = "Hello world";
---
<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>{ title }</title>
</head>
<body>
  <PageHeader />
  <h1>{ title }</h1>
  <ul>
    {
      posts.map((post) => (
        <li><a href={post.slug}>{post.data.title}</a></li>
      ))
    }
  </ul>
</body>
</html>

インデックスページにアクセスしてみると、記事の一覧が表示され、リンクを押すと各記事が表示されるようになりました。

記事の一覧

これで、ひとまず大まかな構成はできました。あとは、各レイアウト・コンポーネント・ページを一所懸命作っていけばよいだけです。なお、この記事では省略していますが、各HTMLタグには直接Tailwindのユーティリティクラスを入れながら、旧サイトのデザインを真似てスタイルも同時に設定していきました。

こうして最終的な構成はこのような感じになりました。

(一部省略しています)
.
├── README.md
├── astro.config.mjs
├── package-lock.json
├── package.json
├── public
│   ├── 202007_01
│   │   └── cover.png
│   ├── 202007_02
│   │   └── cover.png
│   │       :
│   └── images
├── src
│   ├── components
│   │   ├── ArticleCard.astro
│   │   ├── ArticleCardWide.astro
│   │            :
│   ├── consts.js
│   ├── content
│   │   └── blog
│   │       ├── 202007_01
│   │       │   ├── images
│   │       │   └── index.md
│   │       ├── 202007_02
│   │       │   ├── images
│   │       │   └── index.md
│   │                :
│   ├── env.d.ts
│   ├── layouts
│   │   ├── PageLayout.astro
│   │   └── PostLayout.astro
│   ├── pages
│   │   ├── 404.astro
│   │   ├── [...slug].astro
│   │   ├── about.astro
│   │   ├── archive
│   │   │   ├── [page].astro
│   │   │   └── index.astro
│   │   ├── index.astro
│   │   ├── rss.xml.js
│   │   └── tag
│   │       ├── [tag].astro
│   │       └── index.astro
│   └── styles
│       ├── markdown.css
│       └── prism-dracula.css
└── tailwind.config.cjs

さいごに

記事の移行と残る課題

Gatsby版で作成した記事は、フロントマターは新しい形式にする必要がありましたが、基本的にはそのまま再利用できました。ただ、カバー画像については、静的ファイルとして扱われ、コンテンツコレクションの中に置いたファイルを扱う方法がわからず、ディレクトリーが分かれてしまっています(publicディレクトリー)。運用上の手間を考えると、1箇所で管理できるよう見直していきたいです。

また、iframeを使った記事があるのですが、このフレーム部分が正しく表示されていませんでした。原因は ViewTransitions という機能を使っていたことによるものでした。数行のコードでリッチなページ遷移アニメーションを行える面白い機能なのですが、ルーティングのしくみが変わり、表示されている内容とカレントページのURLが異なってしまうことでフレームのパスが取得できなくなっていました。今はオフにしていますが、問題を解決して有効にできたらと思います。

まだ、細かな部分で調整が必要な箇所も残ります。これらも運用しながら、より記事が活用されやすくなるようなユーザーストーリーを描いて、改善を進めていく予定です。

Astroへ移行してみて

Gatsby版は、ブラックボックスのまま運用を走ってきた部分が多く、シンプルで動作も早いAstroへ乗り換えられたことでスッキリしました。プロキシーのせいでインストーラーが使えず一から作ることになったのが、結果的に理解を進めたのもよかったと思います。

スモールスタートで作り、自分の知識や技能レベルに合わせて、より作り込んでいけるという点で、サイト構築フレームワークの中ではかなり扱いやすい部類に入ると思います。今後も、ブログサイトに限らず様々なサイト構築に使ってみたいですし、他の方にもオススメできるフレームワークですので、ぜひ使ってみてはいかがでしょうか。


TOP
アルファロゴ 株式会社アルファシステムズは、ITサービス事業を展開しています。このブログでは、技術的な取り組みを紹介しています。X(旧Twitter)で更新通知をしています。