Astro, SolidJS で個人ブログを制作しました

覚え書きです。利用した技術についてまとめます。

    Loading...

S.Inoue と申します。非情報系の大学生ながら個人的に Web 制作に取り組んでおり、大学卒業という節目を迎えるにあたって何か新しく作りたいと思っていました。

特に、精力的に取り組んできた(と思っている)Web フロントエンドの分野でいくつか触ってみたい技術があり、また、以前作ったブログのリプレイスを行うも不満が残る部分が多かったため、いっそ新しく作り直そうということで Astro と SolidJS を使ったブログを制作 しました しております ので、紹介させていただきます。

## 採用した技術

### フレームワーク

#### Astro

Astro
Astro builds fast content sites, powerful web applications, dynamic server APIs, and everything in-between.
Astro favicon astro.build
Astro

静的サイトであるので、SSG に適した Astro を採用しました。パフォーマンスに優れるうえ、シンプルで非常に扱いやすいと感じます。

#### SolidJS

SolidJS
Solid is a purely reactive library. It was designed from the ground up with a reactive core. It's influenced by reactive principles developed by previous libraries.
SolidJS favicon www.solidjs.com
SolidJS

UI フレームワークとして SolidJS を用いました。

JSX を用いるため React によく似ていますが、仮想 DOM を用いないことやフックの記述などに細かな違いがあり、React よりシンプルであるという意見もみられます。React のエコシステムが必要ないのであれば十分に選択肢に入ると思います。

### コンテンツ

#### Content Collections

以前は API ベースの Headless CMS を使っていましたが、手元にコンテンツを置いておきたい気持ちがあったので Astro の Content Collections を用い、ローカルで記事を管理することにしました。

Content collections
Manage your content with type safety.
Content collections favicon docs.astro.build
Content collections

#### GitHub Flavored Markdown

今回は Markdown で入稿できるようにしました。 GitHub Flavored Markdown は Markdown の規格の1つで、Astro で Markdown を扱う場合にはデフォルトとなります。

GitHub Flavored Markdown Spec
GitHub Flavored Markdown Spec favicon github.github.com
MDX について

Astro ではインテグレーションを追加するだけで MDX↗ を簡単に取り入れることができます。
MDX は Markdown 中で JSX を使用することができ、インタラクティブな要素(ボタンなど)を埋め込む場合に有用と思いますが、単なる装飾であれば後述する remark/rehype を利用することで Markdown でも豊富な表現が可能です。したがって、(Markdown と比べて)互換性に乏しい MDX の採用は見送りました。

### 全体のスタイリングとデザイン

#### Tailwind CSS

Tailwind CSS - Rapidly build modern websites without ever leaving your HTML.
Tailwind CSS is a utility-first CSS framework for rapidly building modern websites without ever leaving your HTML.
Tailwind CSS - Rapidly build modern websites without ever leaving your HTML. favicon tailwindcss.com
Tailwind CSS - Rapidly build modern websites without ever leaving your HTML.

流行りものです。Astro や Vue のシングルファイルコンポーネントでは CSS あるいは SASS を採用していましたが、SolidJS でこれらを用いる場合 CSS Modules を扱うことになるため、スタイルが分離することを嫌ったかたちです。

ダークモード対応しやすい点が結構お気に入りです。

CSS-in-JS について

CSS-in-JS は SolidJS でも使うことができますが、選択肢の少なさや、そもそもスタイルとテンプレートを同一ファイルで管理することが目的であれば Tailwind CSS のほうが使いやすく感じたため、見送りました。

#### Solid Icons

React Icons の SolidJS 版にあたるライブラリです。おそらくサードパーティ製ですが、SolidJS のエコシステム↗ として公式に認められています。

GitHub - x64Bits/solid-icons: The simplest way to use icons in SolidJS
The simplest way to use icons in SolidJS. Contribute to x64Bits/solid-icons development by creating an account on GitHub.
GitHub - x64Bits/solid-icons: The simplest way to use icons in SolidJS favicon github.com
GitHub - x64Bits/solid-icons: The simplest way to use icons in SolidJS

#### Web フォント

Noto sans JP, Montserrat および Source Code Pro を用いています。これらは Google Fonts↗ から CDN で読み込むこともできますが、Fontsource を利用して npm パッケージとして扱っています。

GitHub - fontsource/fontsource: Self-host Open Source fonts in neatly bundled NPM packages.
Self-host Open Source fonts in neatly bundled NPM packages. - fontsource/fontsource
GitHub - fontsource/fontsource: Self-host Open Source fonts in neatly bundled NPM packages. favicon github.com
GitHub - fontsource/fontsource: Self-host Open Source fonts in neatly bundled NPM packages.

### 記事のスタイリングとデザイン

#### remark/rehype

記事ページのスタイリングには remark/rehype という処理系を利用しています。
これらは Unified という Markdown - HTML 間の構文解析を取り扱う枠組みの一環として存在します。

unified
Content as structured data: unified compiles content and provides hundreds of packages to work with content
unified favicon unifiedjs.com
unified

プラグインは既存のものが多く存在する(remark↗, rehype↗)ほか、自前で実装することも可能です。例えば、このブログのパラグラフトップレベルの <h2> タグは以下のような rehype プラグインを用意してスタイリングしています。

rehype.ts
import type { ElementContent, Root } from 'hast';
import { visit } from 'unist-util-visit';

export default function rehypeHeading() {
    return (tree: Root) => {
        visit(tree, 'element', (node) => {
            if (node.tagName !== 'h2') return;
            const { value } = node.children[0] as { type: "text", value: string };
            if (!value || typeof value !== 'string') return;

            const hashElm = {
                type: "element",
                tagName: "a",
                properties: {
                    href: `#h2-${value}`,
                    className: "heading bg-gradient-to-r from-accent-sub-base to-accent-base bg-clip-text text-tranparent",
                },
                children: [{ type: "text", value: "#" }],
            } satisfies ElementContent;

            const titleElm = {
                type: "element",
                tagName: "span",
                properties: {},
                children: [{ type: "text", value }],
            } satisfies ElementContent;

            node.children = [hashElm, titleElm];
            node.properties.id = `h2-${value}`;
            node.properties.className = "mt-8 mb-4 lg:mt-16 lg:mb-8 text-2xl sm:text-3xl lg:text-4xl font-bold flex items-center gap-2 w-full pb-2 border-b border-muted-background";
        });
    }
}

そのほか、YouTube や Twitter (X) などの埋め込みも URL を記載するだけで実現できるような remark プラグインを実装しています。
埋め込みは oEmbed に対応しているサイトであればAPIで関連するデータを引っ張ってくることができます。

oEmbed
The oEmbed spec
oEmbed favicon oembed.com

### 記事の検索

全文検索の実装であれば Algolia↗ などのサービスを用いることが多そうですが、今回は Markdown のメタデータのみを検索対象に含めた簡易なクライアントサイド検索を自前で実装しました。

#### Intl.Segmenter

JavaScript 標準の国際化 API である Intl に含まれる Segmenter を用いて日本語の単語分割を実装しました。

Intl.Segmenter - JavaScript | MDN
Intl.Segmenter オブジェクトは、ロケールに応じたテキストのセグメンテーションを可能にし、文字列から意味のある項目(書記素、単語、文)を取得することができます。
Intl.Segmenter - JavaScript | MDN favicon developer.mozilla.org
Intl.Segmenter - JavaScript | MDN

#### Fuse.js

ファジー検索を手軽に実装できる API を提供するライブラリです。

Fuse.js | Fuse.js
Lightweight fuzzy-search library, in JavaScript
Fuse.js | Fuse.js favicon www.fusejs.io

### OG 画像生成

#### satori

Vercel が開発する、JSX から SVG を生成するライブラリです。内部的に React に依存しているのかわかりませんが、JSX がうまく使えなかったのでオブジェクトで記述しています。

GitHub - vercel/satori: Enlightened library to convert HTML and CSS to SVG
Enlightened library to convert HTML and CSS to SVG - vercel/satori
GitHub - vercel/satori: Enlightened library to convert HTML and CSS to SVG favicon github.com
GitHub - vercel/satori: Enlightened library to convert HTML and CSS to SVG

#### Resvg

SVG を PNG に変換してくれるライブラリです。Astro で用いる場合には以下を記述しないとビルド時にエラー↗が出ます。

astro.config.mjs
export const defineConfig({
    vite: {
        ssr: {
            external: ["@resvg/resvg-js"],
        },
        optimizeDeps: {
            exclude: ["@resvg/resvg-js"],
        },
    },
});
GitHub - thx/resvg-js: A high-performance SVG renderer and toolkit, powered by Rust based resvg and napi-rs.
A high-performance SVG renderer and toolkit, powered by Rust based resvg and napi-rs. - thx/resvg-js
GitHub - thx/resvg-js: A high-performance SVG renderer and toolkit, powered by Rust based resvg and napi-rs. favicon github.com
GitHub - thx/resvg-js: A high-performance SVG renderer and toolkit, powered by Rust based resvg and napi-rs.

### ホスティング

#### Cloudflare

独自ドメインの管理を Cloudflare で行っている都合上、ホスティングも Cloudflare で行うことにしました。以前は Vercel を使っていましたが、難なく乗り換えることができました。

どこででも接続、保護、構築
場所を問わず従業員、アプリケーション、ネットワークの速度と安全性を高め、簡略化とコスト削減を実現しましょう。
どこででも接続、保護、構築 favicon www.cloudflare.com
どこででも接続、保護、構築

### CLI

Astro の Content Collections ではローカルの Markdown や JSON, YAML を扱うことになるため、一連のファイル操作をコマンド1つで行うことができると非常に便利です。せっかくなので作ってみることにしました。

#### Commander.js

Node.js のコマンドライン引数を扱うライブラリです。メソッドチェインを駆使して簡単に CLI を構築することができます。

GitHub - tj/commander.js: node.js command-line interfaces made easy
node.js command-line interfaces made easy. Contribute to tj/commander.js development by creating an account on GitHub.
GitHub - tj/commander.js: node.js command-line interfaces made easy favicon github.com
GitHub - tj/commander.js: node.js command-line interfaces made easy

Node.js 標準のファイル操作モジュール (fs) による実装を Commander.js で CLI 化し、tsx で直接実行しています。

GitHub - privatenumber/tsx: ⚡️ TypeScript Execute | The easiest way to run TypeScript in Node.js
⚡️ TypeScript Execute | The easiest way to run TypeScript in Node.js - privatenumber/tsx
GitHub - privatenumber/tsx: ⚡️ TypeScript Execute | The easiest way to run TypeScript in Node.js favicon github.com
GitHub - privatenumber/tsx: ⚡️ TypeScript Execute | The easiest way to run TypeScript in Node.js

#### Chalk

Node.js で実装したコマンドラインに文字色や背景色をつけることができます。こちらの記事↗ によれば、Node.js v21.7.0 以降ではビルトインの機能で同様のことができるようですが、まだ LTS でないのでこのライブラリを使いました。

GitHub - chalk/chalk: 🖍 Terminal string styling done right
🖍 Terminal string styling done right. Contribute to chalk/chalk development by creating an account on GitHub.
GitHub - chalk/chalk: 🖍 Terminal string styling done right favicon github.com
GitHub - chalk/chalk: 🖍 Terminal string styling done right

#### Shellscript

CLI は npm スクリプトとして実行できるようにしてありますが、もう少し簡便に使えるように Shellscript から制御できるようにしてあります。
加えて、Git など定型的な操作をコマンド1つで行えるようにもしました。

## 工夫点や特記事項

### View Transitions API

Astro では、独自のディレクティブを使用して View Transtions API を手軽に実装できます。

ビュートランジション
ビュートランジションを使用して、Astroでページ間のシームレスなナビゲーションを実現する。
ビュートランジション favicon docs.astro.build
ビュートランジション

これを利用して、ブログの一覧表示ページ <-> 記事ページ間の遷移の際に記事のメタデータ部分をアニメーションさせています。

### 読了時間の追加

Astro の公式↗に実装例がありますが、当サイトでは Content Collections を用いているため以下の記事の実装を用いました。

Adding reading time to Astro without the hassle | Blog – Jahir Fiquitiva
An alternative way to calculate reading time without full blog post rendering
Adding reading time to Astro without the hassle | Blog – Jahir Fiquitiva favicon jahir.dev
Adding reading time to Astro without the hassle | Blog – Jahir Fiquitiva

### グローバルステート

こちら↗ で述べられているように、SolidJS の大きな特徴として state がコンポーネント外でも宣言できるというものがあります。よって、状態管理ライブラリを必要とせずとも state を .ts ファイルに隔離し、複数のコンポーネントから参照・更新をすることができます。

このブログにおいては、記事検索のための input 要素を含むUIをデバイスのサイズに応じて出しわけているのですが、バインドするキーワードをグローバルステートにしています。

## 今後の展望

個人で1年半ほど学習・活動してきましたが、実際にモノをつくってみて、Web 制作は非常に奥が深いと感じています。
便利なフレームワークや、先人の知恵が詰まったコードスニペットで下駄を履かせてもらったとしても、まだまだ分からない部分は多いです。

せっかく自分の Web サイトを持てたので、これからはコツコツ記事を書いて知見をためていきたいと思っています。

同じ記事を Zenn にも掲載しております↗。ぜひご覧ください。

## 参考記事

大学のプログラミングサークルのWebサイトをAstro×SolidJSで制作したので技術スタックを紹介
大学のプログラミングサークルのWebサイトをAstro×SolidJSで制作したので技術スタックを紹介 favicon zenn.dev
大学のプログラミングサークルのWebサイトをAstro×SolidJSで制作したので技術スタックを紹介

採用技術を参考にさせていただきました。

Tailwind CSS を使った ダークモード実装の効率的なアプローチ
Tailwind CSS を使った ダークモード実装の効率的なアプローチ favicon zenn.dev
Tailwind CSS を使った ダークモード実装の効率的なアプローチ

Tailwind CSS でカスタムカラーを用いたダークモード対応を設定する方法が紹介されています。

remark プラグインを作って Astro で使う
remark プラグインを作って Astro で使う favicon zenn.dev
remark プラグインを作って Astro で使う

Remark/Rehype プラグインを作成する際に参考にさせていただきました。

記事がありません