Blog

このサイトを Vite + Vanilla JS から Astro に移行した話

このサイトを Vite + Vanilla JS から Astro に移行しました。

併せて、ホスティング先を Github Pages から Netlify に変更したので、旧サイト は、まだ存在しますが今後、運用しません

はじめに

今回、Vite + Vanilla JS で構築していた個人ブログサイトを Astro に移行してみました。

私は Web 開発初心者で HTML / CSS / JavaScript の基礎を学習するために、旧サイトでは Vite + Vanilla JS を選択しました。

詳細:

はじめまして。静的な個人ブログサイトを作りました | R.dev

そして当初から「基礎を学んだらモダンな技術を使って実装しなおそう」と思っていました。

そこで個人のブログサイトを開発するのに一番よさそうなフレームワークとして Astro を選択しました。

Astroを選ぶ理由」の冒頭:

Astroは、ブログやマーケティング、eコマースなど、コンテンツ駆動のウェブサイトを作成するためのウェブフレームワークです。Astroは、新しいフロントエンドアーキテクチャを開拓し、他のフレームワークと比較してJavaScriptのオーバーヘッドと複雑さを低減することで知られています。高速でSEOに優れたウェブサイトが必要なら、Astroが最適です。

Next.js などの選択肢も考えられましたが、私が開発したいサイトには機能が過剰かなと思い選択しませんでした。

移行前後の構成

項目旧サイト新サイト
ビルドツールViteAstro (内部で Vite を使用)
フロントエンドVanilla JSAstro
記事管理MarkdownMarkdown
Markdown処理gray-matter / remark / rehypeAstro (内部で remark / rehype を使用)
コードハイライトShikiShiki
OGP画像生成sharpsharp
テストVitest + jsdom-
ホスティングGitHub PagesNetlify

移行してみた感想

フレームワークなしとの比較になるので、当たり前かもしれませんが、Astro での実装は圧倒的に開発体験が良かったです。

特によかった点としては、次が挙げられます。

  • コンポーネント化・レイアウト化が容易
  • マークダウン記事のページ生成が容易

さらに Netlify でのデプロイもかなり容易でした。

コンポーネント化・レイアウト化が容易

旧サイトでは、ビルドスクリプトでコンポーネントごとに HTML を文字列として作成していました。そのため、ビルドスクリプトの HTML 部分に対して、リントやフォーマット、シンタックスハイライトが効きません。さらにいちいちビルドしないと HTML が生成されないので npm run dev で開発サーバを立ち上げた状態での開発が意味を成しません。ちょっとした変更でも開発体験が悪く大変でした。

旧サイトのビルドスクリプトのイメージ
function renderBaseHead(title, description) {
return `
<head>
<title>${title}</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="${description}" />
</head>`
}

これを Astro では .astro という形式のファイルに React の JSX ライクな実装ができます。

フロントマター部分にはサーバ側で処理されるスクリプトが書けます。その下には、ほとんど生の HTML が書けます。style タグや script タグももちろん使えます。

components/BaseHead.astro
---
const { title, description } = Astro.props;
---
<head>
<title>{title}</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content={description} />
</head>

また使い方も通常の HTML タグのように非常に直感的に扱えると思います。

---
import BaseHead from '@/components/BaseHead.astro';
const title = 'ポストページ';
const description = 'ポストページです。'
---
<html lang="ja">
<BaseHead title={title} description={description} />
...
</html>

さらに slot タグを使用するとレイアウトコンポーネントが簡単に実装できます。

layouts/Layout.astro
---
import BaseHead from '@/components/BaseHead.astro';
import Header from '@/components/Header.astro';
import Footer from '@/components/Footer.astro';
const { title, description } = Astro.props;
---
<html lang="ja">
<BaseHead title={title} description={description} />
<body>
<Header />
<slot />
<Footer />
</body>
</html>

こちらも非常に簡単に使えて、Layout タグで囲った子コンポーネントが slot タグ部分に挿入されます。

---
import Layout from '@/layouts/Layout.astro';
const title = 'ポストページ';
const description = 'ポストページです。'
---
<Layout title={title} description={description}>
<main>
...
</main>
</Layout>

このようにコンポーネントやレイアウトを簡単に切り分けられたり、フロントマター内で処理した結果を HTML に組み込めるのは、可読性、保守性、拡張性が上がって開発が楽だなと思いました。

マークダウン記事のページ生成が容易

旧サイトでは、マークダウン記事のページは次の通り生成していました。

  1. マークダウンを読み込む
  2. gray-matter でマークダウンのフロントマターと本文を分割する
  3. 本文を remarkrehype で HTML 化、Shiki でシンタックスハイライトする
  4. 記事のデータを文字列で構築した HTML に組み込み生成する

Astro にはコンテンツコレクションという概念が存在します。

コンテンツコレクションでは、各記事に共通する構造を定義するスキーマを設定できて、Zod (TypeScript向けのスキーマ宣言・検証ライブラリ) で構造を検証してくれます。

src/content.config.ts
import { glob } from 'astro/loaders';
import { defineCollection } from 'astro:content';
import { z } from 'astro/zod';
const posts = defineCollection({
loader: glob({ pattern: `**/[^_]*.md`, base: './src/posts' }),
schema: z.object({
title: z.string(),
summary: z.string(),
tags: z.array(z.string()),
createdAt: z.date(),
}),
});
export const collections = { posts };

Astro ではファイルベースルーティングを採用していて、動的ルーティングでパラメータを持たせることで、1つのファイルから複数のページを生成できます。

getCollection() でコンテンツデータを取得し、getStaticPaths() で動的ルーティングで持たせるパラメータ (ここでいうと slug)を設定することで各ページを生成します。[slug] ではなく [...slug] とすることで多階層に対応できます。

render() でマークダウンの HTML 変換とコードブロックのシンタックスハイライトなどのプラグイン処理が行われ、Astro コンポーネントが返されます。

HTML 変換関連には remarkrehype が、シンタックスハイライトには Shiki または Prism (選択式) が採用されています。この構成は旧サイトと同じだったのでレンダリング結果も特に違和感なく使用できました。

src/pages/posts/[...slug].astro
---
import { getCollection, render } from 'astro:content';
import PostLayout from '@/layouts/PostLayout.astro';
export async function getStaticPaths() {
const posts = await getCollection('posts');
return posts.map((post, index) => {
params: { slug: post.id },
props: { post },
});
}
const { post } = Astro.props;
const { Content } = await render(post);
---
<PostLayout post={post}>
<Content />
</PostLayout>

Netlify でのホスティングが容易

旧サイトでは、Github Pages にホスティングしていましたが、フリー枠ではプライベートリポジトリでのホスティングができません。 そのため Github Actions を利用して、プライベートリポジトリでソースコードを管理して、パブリックリポジトリにビルド結果のみをプッシュして、サイトを公開する形をとりました。

一方、Netlify では Github のリポジトリを連携するだけで、公開ができました。プライベートリポジトリでも連携可能です。 さらにリポジトリにプッシュすれば、Netlify のビルド処理が走り、レビュー用のサイトURLが提供されるため、マージ前の確認が容易にできます。

旧サイトのデプロイでも特に難しいことがあったわけではないですが、Netlify でのデプロイがあまりにも簡単で驚きました。

おわりに

今回、ブログサイトを Vite + Vanilla JS から Astro へ移行してみて、フレームワークありでの開発があまりにも体験がいいということを改めて感じさせられました。

まずは「移行を完了させる」ことを優先したため、CSSはほとんど以前のものを持ってきています。そのため Astro の特徴に合わせたコンポーネント単位のスタイル整理はできていません。

今後は次を進めていけたらと思います。

  • コンポーネントごとのスタイル責務の整理
  • CSS 構成の見直し
  • Tailwind CSS 導入