鳥小屋.txt

主に自作ゲームをつくったりしているよ。制作に関することやそうじゃないことのごった煮ブログ

Vuepress(v1) で作っていたサイトを Astro(v3) に置き換えた話

たまには Web っぽい話もします!

どういうわけか?

僕は『鳥小屋プラグイン置き場』 というサイトを運用しています。このサイトではRPGツクール用のプラグインを公開しています。元々はこのブログ内でプラグインを公開していたのですが、古い記事の管理が難しかったりといった問題から、独立したWebサイトとして分離・移行しました。

この際に僕が設定した要件は以下の通りです。

  1. ホスティングが簡単であること
    • WordpressなどのCMSを使うとセキュリティアップデートや運用が面倒なため、静的サイトとしてHTMLをアップロードするだけで済む方法が理想でした
    • また、特定のホスティングサービスに依存したくないため、独自ドメインを使用したいと考えました
  2. Markdownでページが作成できること
    • このブログ(はてなブログ)でMarkdownを使用していたため、過去の記事をそのまま移行できると便利です
    • 運用面でも変更が少なくなります

そこで、僕は当時 Vuepress という静的サイトジェネレータを選択しました。

当時、他の候補として Gatsby も考えましたが、 Gatsby は GraphQL を扱う必要があるなど、僕の視点ではやりたいことに対して少し複雑に感じました。また、仕事でReactをよく使用するため、別のものを使ったほうが面白いかなというのもあり、比較的少ない設定で動作するように見えた Vuepress を選びました。

ホスティングには、当初 Vercel を使うつもりでしたが、僕が使っていた無料プランでは Adsense などが規約で禁止されていることがわかったため、 CloudFront + S3 で運用していました。

なぜVuepressをやめるのか

Vuepress自体はとても良いツールです。

しかし3年ほど運用していく中で、以下の課題がありました。

Vuepress と Vitepress

Vuepress の Vite 版とも言える Vitepress があります。

僕の作業環境(WSL1 + HDD)も要因の1つではありますが、 Vuepress は dev server の起動に時間がかかっていました。そこで Vitepress を試してみたところ、起動が非常に早く、開発体験の向上を実感しました。

そのため Vitepress でのリプレイスを検討したのですが……

Vuepress のテーマ(テンプレート)の難しさ

上の図を見るとわかるとおり、このWebサイトは Vuepress のデフォルトテーマとは程遠い見た目をしています。そのため、デフォルトテーマを使用せず、独自のテーマを作成しています。

しかし、Vuepressのテーマのカスタマイズはやや難しいと感じました。Layout ファイルで変更可能なのは body 以下の主要素の部分から下のみだったりと、どの部分までが変更可能なのかがわかりづらく感じました。

また、僕は宗教上の理由で絶対に SCSS を使いたいのですが、Vuepress では palette.stylindex.styl をを用意する必要があります。ちゃんと Vuepress の世界を理解してカスタマイズする必要があり、これにはコストがかかると感じました。

さらに、Vitepress に移行する場合、 Vuepress で使ってたプラグインなどが一部非対応になりますし、今後 Vue3 に書き換えることなどを考えると、テーマの再調整は必須となります。これらのことから、僕の中で「テーマいじりたくないから、Vuepress をやめて別の何かに置き換える」 検討を始める大きな要因となりました。

つまり、Vuepressのレールから外れたことをしてしまったのが敗因ですね。

再検討:何を使うのか?

要件は以下です。

  • 引き続き、静的サイトジェネレータを使う
    • ただしテーマ(見た目)変更の自由度は高いほうがいい ←New!
  • 仕事でよく使用するReactを使わない

これらの要件を満たすために、次の3つの選択肢から選ぶことにしました。

Nuxt.js

Vue の Next.js 的なやつ。

同じ Vue であるという点と、過去に別のWebサイトを作成する際にも使ったことから候補の筆頭です。

本来であれば Vuepress で使用していたコンポーネントをそのまま移行できるといったメリットもあるはずでしたが、僕の使用していたコンポーネントは古い書き方かつTypeScriptも使用していなかったため、そのメリットは享受できなさそうです。

Sveltekit

Svelte のフレームワークです。

僕のサークル活動の拠点である『鳥小屋ポータRu』のWebサイトで使用しています。現在も利用し続けているフレームワークで個人的にも使い勝手が良いと感じており、移行作業をするとしても比較的スムーズにできることが期待できます。もし、僕が個人でSPA(Single Page Application)のWebサイトを作る場合の第一候補だと考えています。

ただし、仕事ではなく個人開発であるということを考えると、今まで使ったことがないものを試したほうが良いのではないか?と考えました。

Astro

そんなわけで Astro くんです。

AstroはSPAではなく、各ページが独立したMPA(Multi Page Application)のHTMLとして出力される静的サイトジェネレータです。そのため、リンクをクリックすると通常のWebページのように次のHTMLファイルのページに遷移します。

今回のWebサイトは主に記事(文章)が中心であり、インタラクティブな要素が少ないため、SPAであることに強いこだわりはありませんでした。また、Astroの記法はSvelte + JSXに似ており、個人的にはReact + JSXのコンポーネントよりも書きやすそうに感じました。

また肝心のレイアウトについても、非常に自由度が高く設定できそうです。

以上のことから、

  • 使ったことがない新しいツールを試してみたい
  • 必ずしもSPAである必要がない
  • 見た目の調整をする際に制約が少ない

といった理由から、今回は Astro を採用することにしました。

余談

以前にAstroを試そうとしたことがありましたが、当時はJetbrainsのIDEがAstroに対応しておらず、挫折した経験がありました。現在はAstroのプラグインが提供されており、IntelliJ IDEAやWebStormでIDEのサポートを受けることができます。

移行するためにしたこと

基本的に移行に際して、記事の内容を書き直したり、ページのデザインを変えたりはしません。
(Markdownの変換結果のHTML構造など、微妙にちょっと変わることは許容します)

コンテンツコレクションを使う

Astro v2から導入されたコンテンツコレクション機能を利用しました。

コンテンツコレクションでは src/content 以下に Markdown などのファイルを置いて、ページ内から利用することができます。また、この際に Zod を利用してコレクションの型定義を書くことができます。

今回、Vuepressで使用していたMarkdownの記事を極力そのまま移行する上で、Markdown ファイル内の Frontmatter の項目を機械的に検証可能なのは非常に便利でした。

実際には src/pages/[...slug].astro というルーティング用のファイルを作成し、以下のような内容を記載するだけで Vuepress の Markdown からほぼ編集することなく移行することができました。ただし Vuepress では記事の URL を permalink という名前で記述していたため、その部分だけ slug に置換しました。

---
import { getCollection, CollectionEntry } from 'astro:content';
import BaseLayout from '../layouts/BaseLayout.astro';
interface Props {
  doc: CollectionEntry<'docs'>;
}

export async function getStaticPaths() {
  const allDocs = await getCollection('docs');

  return allDocs
    .filter((doc) => doc.slug !== '/') // ルートは省く!
    .map((doc) => ({
    params: {
      slug: doc.slug,
    },
    props: { doc }
  }));
}

const { doc } = Astro.props;
const { Content, headings } = await doc.render();
---
<BaseLayout doc={doc} headings={headings}>
  <Content />
</BaseLayout>

ただし src/pages/[...slug].astro では / へのアクセスを受けることができませんでした。そのため、 / 専用に別途 src/pages/index.astro を作成することにしました。それにあわせて [...slug].astro のほうでは getStaticPaths/ を含めないようにしています。

各種コンポーネントの移行

Vuepressで作成していたコンポーネントを、すべてAstroで書き直しました。

テンプレートとCSSは、記法が異なるループ部分などを除いてほぼコピー&ペーストでそのまま利用できました。

JavaScript部分については、GoogleAnalyticsのページ遷移時の処理の記載など、そもそもMPAになったことで不要となる部分が多かったため、コード量が大幅に減少しました。こういった面でも、出力後のページの中にAstro固有の仕組みが存在しないことは大きなメリットだと感じました。

サイトマップの作成

Astroの公式インテグレーションである @astrojs/sitemap を使おうとしましたが、config の site 指定などを行っても生成後のサイトマップの中身が意図した内容にならなかったため、自分でサイトマップを吐き出すインテグレーションを書きました。

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'node:url';
import type { AstroIntegration, AstroConfig } from 'astro';

const mySitemap = (): AstroIntegration => {
  let config: AstroConfig;

  return {
    name: 'mySitemap',
    hooks: {
      'astro:config:done': async ({ config: cfg }) => {
        config = cfg;
      },
      'astro:build:done': async ({ dir, pages }) => {
        const host = config.site;

        const ignorePaths = ['/404/'];
        const distDir = fileURLToPath(dir);

        const xml = `<?xml version="1.0" encoding="UTF-8" ?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml" 
xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" 
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" 
xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
${pages
  .map(({pathname}) => pathname || '/')
  .filter((pathname) => !ignorePaths.includes(pathname))
  .map((pathname) => `<url><loc>${host}${pathname}</loc></url>`)
  .join('\n')}
</urlset>`;

        fs.writeFileSync(path.join(distDir, 'sitemap.xml'), xml, 'utf-8');
      },
    },
  };
};

export default mySitemap;

XMLを真面目に作る気がない不届き者。

404ページ

元々、 CloudFront + Lambda@Edge 側でページが見つからなかった場合に /404.html の内容を返すという設定を行っていました。そのため、Astro でも /404.html の生成を行う必要があります。

しかし、何も考えずに生成すると /404/index.html となってしまうため、今回は生成後に mv dist/404/index.html dist/404.html を実行するというその場しのぎの対応を行っています。

余談:Cloudflare Pages への移行

Google Domains のサービス終了を受けて、今まで Google Domains + Route53 で管理してた rutan.dev ドメインを Cloudflare に移管しました。そのため、ついでに Astro 化のタイミングでホスティング先を Cloudflare Pages に変更しました。特にいろいろ設定をしなくても簡単にデプロイができて便利でした。

おわり

Astroは移行作業はスムーズに進行でき、だいたい1日くらいで移行することができました。妙なハマりどころもそこまでなく、非常に使いやすい印象を受けました。また僕の悩みの種であったレイアウト周りについても自由度が高く、かなり満足いく結果です。

今後 Astro のメジャーバージョンが変わった場合にどのくらい影響があるかは未知数ですが、コード量自体は多くないためあまり困らずにアップデートしていけるといいなぁ~。