mooriii's blog

article icon

HonoXのSSGで個人ブログを構築しました

個人ブログを開設する機運が高まっていたところに、HonoXという試したい技術が出てきたので勉強がてら個人ブログを作ってみました。

今回ブログを構築する過程で調べたこと等をまとめていきます。

HonoXでSSGを始める

HonoXとは @yusukebeさんが作っているHonoとViteを組み合わせたフルスタックのWebフレームワークです。 SSRが高速だったりCloudflareとの相性が抜群だったり何かとイケているフレームワークだったので時代の波に乗って使ってみました。

手順 に沿って始めればすぐに動きます。

GitHub - honojs/honox: HonoX - Hono based meta frameworkHonoX - Hono based meta framework. Contribute to honojs/honox development by creating an account on GitHub.
favicon of https://github.com/honojs/honoxgithub.com
ogp of https://opengraph.githubassets.com/a712cf440e61563c69e9fb0744eb8be35266e7b0bb9f296fe1d8abfd1eca86d2/honojs/honox

SSGは hono にある @hono/vite-ssg プラグインを使えば簡単に出来て、以下のようにvite.config.ts に追加すればビルド時に静的ファイルを吐き出してくれます。

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)
honox/src/client/client.ts
  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-frontmatterremark-mdx-frontmatterを使うと、mdxファイルの先頭の --- で囲んだ部分をメタデータっぽく扱うことが出来ます。

---
title: 記事のタイトル
description: 説明
---
 
(記事の内容)

_renderer.tsx で設定した値をpropsとして受け取ることが出来ます。

export default jsxRenderer(({ children, frontmatter }) => {
  const title = frontmatter?.title ?? "" // 記事のタイトル
  const description = frontmatter?.description ?? "" // 説明

マークダウン記法で使われるコンポーネントを独自のものに差し替える

useMdxComponents を定義してあげれば、マークダウン記法で使用されるコンポーネントを差し替えることが出来ます。

![画像のタイトル](画像のURL)

例えばこんな感じで画像を参照させても、内部的に独自の画像用コンポーネントを使うことも可能です。

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"/>

実際の表示↓↓↓

mooriii's Portfolio開発したプロダクトや経歴、趣味をまとめています
favicon of https://user-images.githubusercontent.com/24749358/107361593-f9d8f880-6b1a-11eb-8c78-77cc1deea964.pngmooriii.com
ogp of https://user-images.githubusercontent.com/24749358/107361593-f9d8f880-6b1a-11eb-8c78-77cc1deea964.png

ExternalOgpの実装はこちら

シンタックスハイライトを導入する

今回はRehype Pretty Codeを使ってシンタックスハイライトを実現しましたが、これもかなり強力で感動しっぱなしでした。

こんな感じで行数表示とか行ハイライトとかいい感じにやってくれます。

sample.tsx
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さんの記事リポジトリを参考にさせてもらいながら進めました。

HonoXでsatoriを使ってOGイメージもSSGするHonoXで最近実装しなおしたこのサイトにOGイメージが出せるようにしました。画像をSSGするまでにいろいろ試行錯誤があったので記録します。
favicon of blog.berlysia.net
ogp of https://blog.berlysia.net/ogimage/entry/2024-02-29-honox-og-image.png

動的ルーティングでいい感じにしたことと、ビルドの設定やbudouxの導入に躓いた事は次回以降の記事で書いていきます。

結果的にmdxから記事のタイトルを抽出したOGP画像を生成してくれるようになりました。

ogp

OGP画像のデバッグは Vercel OG Image Playgroundが便利でした。

ダークモード対応

Tailwindを使ってスタイルを当てているので、ダークモードも比較的簡単に実現できました。

Dark Mode - Tailwind CSSUsing Tailwind CSS to style your site in dark mode.
favicon of https://tailwindcss.com/docs/dark-modetailwindcss.com
ogp of https://tailwindcss.com/api/og?path=/docs/dark-mode

ただし、cssが読み込まれる前にテーマの設定をしてあげないと、ページ遷移時やリロード時にチラつきが発生してしまいます。

<style>タグの前にテーマ設定のスクリプトを仕込んであげればチラつきは解消できます。

AndroidのBFCacheに対応する

上記でチラつきは解消したのですが、Android端末でページ遷移してテーマを変更した後、ブラウザバッグをするとテーマが戻ってしまうという挙動を観測しました。

調べて見るとどうやらBFCache(Back Forward Cache)というものが影響していたみたいです。 (どういう条件で発火するのかは別でちゃんと調べたい)

ブラウザの戻る/進むを高速に!ヤフーにおけるBFCache有効化に向けた取り組みヤフー全社で実施した「BFCacheの有効化」の取り組みについて、Yahoo!ニュースのビジネス指標向上の事例を交えて具体的に解説します。
favicon of https://techblog.yahoo.co.jp/entry/2023072430429932/techblog.yahoo.co.jp
ogp of https://s.yimg.jp/images/tecblog/2023-H1/264/ogp.png

BFCacheが原因でテーマ設定のスクリプトが発火しないことによって、遷移後に設定したテーマが反映されていなかったので、pageshow イベントのリスナーをセットすることによって回避しました。

// androidのブラウザバックのback forward cache対策
window.addEventListener("pageshow", applyTheme);

bun shellで記事の雛形を作ってもらう

本ブログの記事のmdxファイルは articles/yyyyMM/yyyyMMdd/**.mdx というディレクトリの中に保存しています。 (絶対に日付順で並んでいて欲しいのと、記事が溜まってきてもフォルダの中身が多くなりすぎないようにしたいという強いこだわりの結果こうなりました🥹)

いちいち該当ディレクトリにファイルを作成したり、記事のメタデータ(frontmatter)に作成日を入れるのが面倒だったので、bun shellでファイルとメタデータの生成をやってもらいます。

the creation of mdx templates

shellの実行が完了するとこんな感じのファイルが吐き出されます。

---
title: 最初の記事
date: 2024-06-01T13:16:07.130Z
description: 
---

shellの最後にエディタで作成したファイルを開く記述をしているので、作成後にエディタで開いてくれるのですが、これが地味に便利です。

await $`code ./app/articles/${yyyyMM}/${yyyyMMdd}/${entryPath}.mdx`;

おわりに

久しぶりに新しい技術をたくさん触れたのでかなり楽しい開発でした。HonoXの開発体験も神がかっていて最高の機会になりました。

今回使った技術は、どれも少し設定しただけで色々よしなにやってくれすぎて、内部で何をやっているか分からないまま作れちゃいました。

それ故にエラーに出会っても全然解消出来なかったりしたので、ちゃんと深いところまで潜って勉強しないとな〜という内省にも繋がりました。

実際はかなりどん詰まりしたので、「何につまってどう解決していったのか」を次回以降少しずつ書いていこうかなと思っています。

このブログ自体も少しずつアップデートできたらいいな〜なんて思ってます。

最後は愛猫のらての写真でお別れしましょう。

それでは。

愛猫のらてさん

この記事をシェアするx icon
アイコン画像
Takuto Mori@_mooriii

Wevoxというサービスのフロントエンジニアをしています。趣味は猫を眺めることです🐱