個人ブログを開設する機運が高まっていたところに、HonoXという試したい技術が出てきたので勉強がてら個人ブログを作ってみました。
今回ブログを構築する過程で調べたこと等をまとめていきます。
HonoXでSSGを始める
HonoXとは @yusukebeさんが作っているHonoとViteを組み合わせたフルスタックのWebフレームワークです。 SSRが高速だったりCloudflareとの相性が抜群だったり何かとイケているフレームワークだったので時代の波に乗って使ってみました。
手順 に沿って始めればすぐに動きます。
SSGは hono
にある @hono/vite-ssg
プラグインを使えば簡単に出来て、以下のようにvite.config.ts
に追加すればビルド時に静的ファイルを吐き出してくれます。
import honox from 'honox/vite'
import ssg from '@hono/vite-ssg'
import { defineConfig } from 'vite'
const entry = './app/server.ts'
export default defineConfig(() => {
return {
plugins: [honox(), ssg({ entry })]
}
})
クライアントコードを分離する
SSGはそのままだとクライアントjsを動かせないので、 islands
コンポーネントを使用して、サーバー側でレンダリングされたHTMLに対して後からハイドレーションを行っているようです。
そうすることによってインタラクティブな部分以外を先にサーバー側で完成させることが出来て、結果的にパフォーマンスとUXの向上が見込めます。
islands
コンポーネントは以下のルールで作成すると認識されます。(間違ってたらすいません)
/islands
ディレクトリに書く_**.island.tsx
というファイル名にする- 先頭に
$
をつける($hoge.tsx
)
const FILES = options?.ISLAND_FILES ?? {
...import.meta.glob('/app/islands/**/[a-zA-Z0-9-]+.tsx'),
...import.meta.glob('/app/**/_[a-zA-Z0-9-]+.island.tsx'),
...import.meta.glob('/app/**/$[a-zA-Z0-9-]+.tsx'),
}
rendererはデフォルトでは hono/jsx
のものが使われますが、READMEに書かれているように、React等のrendererを使うこともできるみたいです。が、今回は試してません。
You can bring your own renderer using a UI library like React, Preact, Solid, or others.
MDXを使えるようにする
今回初めてちゃんとMDXに触れたんですが、こいつの破壊力が抜群すぎて感動しました。Storybookとかで少しだけ触ったことあったんですが想像以上でした。
MDXとは
MDXとは、簡単に言うとMarkdownの中でJSXが書けちゃうもので、独自のコンポーネントを使った拡張も出来てかなり執筆体験が良いです。
例えばこんな感じです。
## テスト
ポートフォリをサイトを作りました
<a href="https://mooriii.com">リンク</a>
<ExternalOgp url="https://mooriii.com"/>
みたいな感じでReactのノリでMarkdownの中に独自で定義したコンポーネントを呼び出すことが出来ます。
frontmatterでメタデータを設定する
remark-frontmatter
と remark-mdx-frontmatter
を使うと、mdxファイルの先頭の ---
で囲んだ部分をメタデータっぽく扱うことが出来ます。
---
title: 記事のタイトル
description: 説明
---
(記事の内容)
_renderer.tsx
で設定した値をpropsとして受け取ることが出来ます。
export default jsxRenderer(({ children, frontmatter }) => {
const title = frontmatter?.title ?? "" // 記事のタイトル
const description = frontmatter?.description ?? "" // 説明
マークダウン記法で使われるコンポーネントを独自のものに差し替える
useMdxComponents
を定義してあげれば、マークダウン記法で使用されるコンポーネントを差し替えることが出来ます。

例えばこんな感じで画像を参照させても、内部的に独自の画像用コンポーネントを使うことも可能です。
type Props = {
src: string;
alt: string;
};
const ArticleImage = (props: Props) => {
return (
<figure class="full-width justify-center flex">
<a href={props.src}>
<img src={props.src} alt={props.alt} />
</a>
</figure>
);
};
export function useMDXComponents(): MDXComponents {
const components = {
ExternalOgp: ExternalOgp, // 独自コンポーネントの追加
img: ArticleImage, // 既存コンポーネントの上書き
};
return components;
}
HTMLタグに無い独自のコンポーネントも追加できるので、mdxファイル内でJSXを書いてあげるといい感じにしてくれます。
## カスタムのコンポーネント
<ExternalOgp url="https://mooriii.com"/>
実際の表示↓↓↓

(ExternalOgp
の実装はこちら)
シンタックスハイライトを導入する
今回はRehype Pretty Codeを使ってシンタックスハイライトを実現しましたが、これもかなり強力で感動しっぱなしでした。
こんな感じで行数表示とか行ハイライトとかいい感じにやってくれます。
import { useFloating } from "@floating-ui/react";
function MyComponent() {
const { refs, floatingStyles } = useFloating();
return (
<>
<div ref={refs.setReference} />
<div ref={refs.setFloating} style={floatingStyles} />
</>
);
}
設定方法等はまた次の機会に。
OGP画像もSSGのビルド時に生成するようにした
今回はSSGで事前にHtmlを生成するようにしたのですが、satoriを使ってビルド時に動的なOGP画像も自動生成することにしました。
@berlysiaさんの記事とリポジトリを参考にさせてもらいながら進めました。

動的ルーティングでいい感じにしたことと、ビルドの設定やbudouxの導入に躓いた事は次回以降の記事で書いていきます。
結果的にmdxから記事のタイトルを抽出したOGP画像を生成してくれるようになりました。
OGP画像のデバッグは Vercel OG Image Playgroundが便利でした。
ダークモード対応
Tailwindを使ってスタイルを当てているので、ダークモードも比較的簡単に実現できました。
ただし、cssが読み込まれる前にテーマの設定をしてあげないと、ページ遷移時やリロード時にチラつきが発生してしまいます。
<style>
タグの前にテーマ設定のスクリプトを仕込んであげればチラつきは解消できます。
AndroidのBFCacheに対応する
上記でチラつきは解消したのですが、Android端末でページ遷移してテーマを変更した後、ブラウザバッグをするとテーマが戻ってしまうという挙動を観測しました。
調べて見るとどうやらBFCache(Back Forward Cache)というものが影響していたみたいです。 (どういう条件で発火するのかは別でちゃんと調べたい)

BFCacheが原因でテーマ設定のスクリプトが発火しないことによって、遷移後に設定したテーマが反映されていなかったので、pageshow
イベントのリスナーをセットすることによって回避しました。
// androidのブラウザバックのback forward cache対策
window.addEventListener("pageshow", applyTheme);
bun shellで記事の雛形を作ってもらう
本ブログの記事のmdxファイルは articles/yyyyMM/yyyyMMdd/**.mdx
というディレクトリの中に保存しています。
(絶対に日付順で並んでいて欲しいのと、記事が溜まってきてもフォルダの中身が多くなりすぎないようにしたいという強いこだわりの結果こうなりました🥹)
いちいち該当ディレクトリにファイルを作成したり、記事のメタデータ(frontmatter)に作成日を入れるのが面倒だったので、bun shellでファイルとメタデータの生成をやってもらいます。
shellの実行が完了するとこんな感じのファイルが吐き出されます。
---
title: 最初の記事
date: 2024-06-01T13:16:07.130Z
description:
---
shellの最後にエディタで作成したファイルを開く記述をしているので、作成後にエディタで開いてくれるのですが、これが地味に便利です。
await $`code ./app/articles/${yyyyMM}/${yyyyMMdd}/${entryPath}.mdx`;
おわりに
久しぶりに新しい技術をたくさん触れたのでかなり楽しい開発でした。HonoXの開発体験も神がかっていて最高の機会になりました。
今回使った技術は、どれも少し設定しただけで色々よしなにやってくれすぎて、内部で何をやっているか分からないまま作れちゃいました。
それ故にエラーに出会っても全然解消出来なかったりしたので、ちゃんと深いところまで潜って勉強しないとな〜という内省にも繋がりました。
実際はかなりどん詰まりしたので、「何につまってどう解決していったのか」を次回以降少しずつ書いていこうかなと思っています。
このブログ自体も少しずつアップデートできたらいいな〜なんて思ってます。
最後は愛猫のらての写真でお別れしましょう。
それでは。