S.Inoue と申します。大学生(非情報系)ですが、個人的に Web 制作に取り組んでおり、大学卒業という節目を迎えるにあたって何か新しく作りたいと思っていました。
特に、精力的に取り組んできた(と思っている)Web フロントエンドの分野でいくつか触ってみたい技術があり、また、以前作ったブログのリプレイスを行うも不満が残る部分が多かったため、いっそ新しく作り直そうということで Astro と SolidJS を使ったブログを制作 しました しております1 ので、紹介させていただきます。
## 使用した技術
### フレームワーク
#### Astro
静的サイトであるので、SSG に適した Astro を採用しました。パフォーマンスに優れるうえ、シンプルで非常に扱いやすいと感じます。
#### SolidJS
UI フレームワークとして SolidJS を用いました。
JSX を用いるため React によく似ていますが、仮想 DOM を用いないことやフックの記述などに細かな違いがあり、React よりシンプルであるという意見もみられます。React のエコシステムが必要ないのであれば十分に選択肢に入ると思います。
### コンテンツ
#### Content Collections
以前は API ベースの Headless CMS を使っていましたが、手元にコンテンツを置いておきたい気持ちがあったので Astro の Content Collections を用い、ローカルで記事を管理することにしました。
#### GitHub Flavored Markdown
今回は Markdown で入稿できるようにしました。 GitHub Flavored Markdown は Markdown の規格の1つで、Astro で Markdown を扱う場合にはデフォルトとなります。
MDX について
Astro ではインテグレーションを追加するだけで MDX↗ を簡単に取り入れることができます。
MDX は Markdown 中で JSX を使用することができ、インタラクティブな要素(ボタンなど)を埋め込む場合に有用と思いますが、単なる装飾であれば後述する remark/rehype を利用することで Markdown でも豊富な表現が可能です。したがって、(Markdown と比べて)互換性に乏しい MDX の採用は見送りました。
### 全体のスタイリングとデザイン
#### Tailwind CSS
流行りものです。Astro や Vue のコンポーネントのスタイリングでは今まで CSS あるいは SASS を採用していましたが、SolidJS/JSX でそれらを用いる場合 CSS Modules を扱うことになるため、スタイルが分離することを嫌ったかたちです。
ダークモード対応しやすい点が結構お気に入りです。
CSS-in-JS について
CSS-in-JS は SolidJS でも使うことができますが、選択肢の少なさや、そもそもスタイルとテンプレートを同一ファイルで管理することが目的であれば Tailwind CSS のほうが使いやすく感じたため、見送りました。
#### Solid Icons
React Icons の SolidJS 版にあたるライブラリです。サードパーティ製(おそらく)ですが、SolidJS のエコシステム↗ として公式に認められています。
#### Web フォント
Noto sans JP, Montserrat および Source Code Pro を用いています。これらは Google Fonts↗ から CDN で読み込むこともできますが、Fontsource を利用して npm
パッケージとして扱っています。
### 記事のスタイリングとデザイン
#### remark/rehype
記事ページのスタイリングにおいて remark/rehype という処理系を利用しています。
これらは unified という Markdown - HTML 間の構文解析を取り扱う枠組みの一環として存在します。
既製のプラグインが多く存在する(remark↗, rehype↗)ほか、自前で実装することも可能です。例えば、このブログのパラグラフトップレベルの <h2>
タグは以下のような rehype プラグインを用意してスタイリングしています。
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) をはじめとした Web サービスの埋め込みも、URL を記載するだけで実現できるような remark プラグインを実装しています。
埋め込みは oEmbed API を公開しているサイトであれば簡単に HTML を取得できますし、そうでない場合も何かしらの 外部 Web API を叩いた結果をもとに HTML を返すような仕組みを実装できます。
### 記事の検索
全文検索の実装であれば Algolia↗ などのサービスを用いることが多そうですが、今回は Markdown のメタデータ(フロントマター)のみを検索対象に含めた簡易なクライアントサイド検索を自前で実装しました。
#### Intl.Segmenter
JavaScript 標準の国際化 API である Intl
に含まれる Segmenter
を用いて日本語の単語分割を実装しました。
#### Fuse.js
ファジー検索を手軽に実装できるライブラリです。
### OG 画像生成
#### satori
Vercel が開発した、JSX から SVG を生成するライブラリです2。
#### Resvg
SVG を PNG に変換してくれるライブラリです。Astro で用いる場合にはこの内容↗を記述しないとビルド時にエラーが出ます。
export const defineConfig({
vite: {
ssr: {
external: ["@resvg/resvg-js"],
},
optimizeDeps: {
exclude: ["@resvg/resvg-js"],
},
},
});
### ホスティング
#### Cloudflare Pages
独自ドメインの管理を Cloudflare で行っている都合上、ホスティングも Cloudflare で行うことにしました。以前は Vercel を使っていましたが、難なく乗り換えることができました。
今後バックエンドを実装する機会があれば、Workers や D1 の利用も考えたいと思っています。最近話題のバックエンドフレームワークである Hono↗ も使ってみたいです。
### CLI
Astro の Content Collections ではローカルの Markdown や JSON, YAML を扱うことになるため、一連のファイル操作をコマンド1つで行うことができると非常に便利です。せっかくなので作ってみることにしました。
#### Commander.js
Node.js のコマンドライン引数を扱うライブラリです。メソッドチェインを駆使して簡単に CLI を構築することができます。
Node.js 標準のファイル操作 API による処理を Commander.js で CLI 化し、tsx
で実行できるようにしています。
#### Chalk
Node.js で実装したコマンドラインに文字色や背景色をつけることができます。こちらの記事↗ によれば、Node.js v21.7.0 以降ではビルトインの機能で同様のことができるようですが、まだ LTS でないのでこのライブラリを使いました。
#### Bash スクリプト
定型的な Git 操作を少ないコマンドで行えるように Bash スクリプトを別途組んであります。また、Commander.js で構築した CLI を経由して実行するスクリプトなども作成してあります。
## 工夫点や特記事項
### View Transitions API
Astro では、独自のディレクティブを使用して View Transtions API を手軽に実装できます。
これを利用して、ブログの一覧表示ページ <-> 記事ページ間の遷移の際に記事のメタデータ部分をアニメーションさせています。
Astrov5
での仕様変更
View Transition API は、v5
から Client Router という名称で扱われるように変更されたようです。詳しくは https://docs.astro.build/en/guides/upgrade-to/v5/↗ を参照してください。
### 読了時間の追加
Astro の公式↗に実装例がありますが、当サイトでは Content Collections を用いているため以下の記事の実装を用いました。
### グローバルステート
こちら↗ で述べられているように、SolidJS の大きな特徴として state がコンポーネント外でも宣言できるというものがあります。よって、状態管理ライブラリを必要とせずとも state を .ts
ファイルに隔離し、複数のコンポーネントから参照・更新をすることができます。
このブログにおいては、記事検索のための input
要素を含むUIをデバイスのサイズに応じて出しわけているのですが、バインドするキーワードをグローバルステートにしています。
### お問い合わせフォームの作成
以下の記事に実装をまとめています。
### コメント欄の追加
以下の記事に実装をまとめています。
### Zenn, Qiita への記事のエクスポート
以下の記事に実装をまとめています。
## 今後の展望
個人で1年半ほど学習・活動してきましたが、実際にモノをつくってみて、Web 制作は非常に奥が深いと感じています。
便利なフレームワークや、先人の知恵が詰まったコードスニペットで下駄を履かせてもらったとしても、まだまだ分からない部分は多いです。
せっかく自分の Web サイトを持てたので、これからはコツコツ記事を書いて知見をためていきたいと思っています。
同じ記事を Zenn にも掲載しております↗。ぜひご覧ください。
## 参考記事
採用技術を参考にさせていただきました。
Tailwind CSS でカスタムカラーを用いたダークモード対応を設定する方法が紹介されています。
Remark/Rehype プラグインを作成する際に参考にさせていただきました。