ratete.dev を支える技術と設計判断
このリポジトリで使えるすべての表示機能を、リポジトリ自身の構造とアーキテクチャを題材に網羅的に紹介する自己説明的サンプル。
個人ブログを作るだけなら、既存のテンプレートを使えば半日で終わる。それでもゼロから組んだのは、記事を書く体験そのものを自分好みに仕上げたかったからだ。MDXで書いた原稿がビルド時にリッチなHTMLへ変換され、クライアントJavaScriptにはほぼ依存しない。この記事ではratete.devの設計と実装を、実際の構成を見せながら解説する。
モノレポの全体像
Bunワークスペースを使ったモノレポ構成を採用している。主要な構成は以下の通り。
| パス | 内容 |
|---|---|
apps/web | Astro 6メインサイト |
apps/article-preview-cli | CLIプレビューツール |
apps/article-preview-chrome | Chrome拡張プレビュー |
apps/article-preview-vscode | VS Code拡張プレビュー |
packages/article-renderer | 共有レンダリングライブラリ |
ここで重要なのは、apps/webは自己完結するという方針だ。
このモノレポではwebを共通化のために歪めないという方針をとっている。共有が必要なロジックはpackages/article-rendererに切り出し、CLI・Chrome拡張・VS Code拡張がそれを参照する。
Web本体とサブアプリ間でロジックを共有せず、二重実装を許容する。「DRY原則に反するのでは?」と思うかもしれないが、Webサイトの表示最適化とプレビューツールの汎用性はしばしば相反するため、無理に共通化すると両方が中途半端になる。
apps/webは自己完結とし、共有資産(packages/*)を参照しない。サブアプリ群はpackages/*を正本として参照する。webとサブアプリ間でロジックを共有せず二重実装を許容する。
記事がブラウザに届くまで
MDXファイルがどのようにHTMLへ変換されるのか。rehypeプラグインチェーンが中核を担っている。
各プラグインの責務を整理しておく。
| rehypeプラグイン | ファイル | トリガー | 出力 | 実行順 |
|---|---|---|---|---|
rehypeUrlCard | url-card.ts | ベアURLのみの段落 | OGPカード | 1 |
rehypeMermaid | mermaid.ts | ```mermaidコードブロック | インラインSVG | 2 |
rehypeCodeBlock | code-block.ts | すべての<pre>要素 | ヘッダー・コピー・折りたたみ付き | 3 |
rehypeTableWrap | table.ts | すべての<table>要素 | div.table-wrapでラップ | 4 |
rehypeHeadingLink | heading-link.ts | すべての<h2>要素 | リンクコピーボタン追加 | 5 |
より詳しいデータフローをシーケンス図で見てみよう。
注目すべきは、すべてがビルド時に完結するという点だ。OGPの取得もMermaidのSVG描画もビルド中に行われ、クライアント側には静的HTMLだけが届く。
コンテンツスキーマの設計
AstroのContent Collectionで記事のメタデータを型安全に管理している。スキーマ定義は以下の通り。
import { defineCollection, z } from "astro:content";
import { glob } from "astro/loaders";
const articles = defineCollection({
loader: glob({ pattern: "*.mdx", base: "./src/content/articles" }),
schema: z.object({
badge: z.string(),
title: z.string(),
lead: z.string(),
published: z.string(),
updated: z.string(),
tags: z.array(z.string()).default([]),
}),
});エンティティ間の関係をER図で表すとこうなる。
Mermaidダイアグラムの事前描画
Mermaidの描画方式が、このサイトで最も技術的に面白い部分かもしれない。一般的なMermaidの利用法ではクライアント側でJavaScriptを実行して描画するが、ratete.devではビルド時にPlaywrightを起動してSVGを事前生成する。
Mermaid図はビルド時にインラインSVGへ事前描画し、実行時のCDN・クライアント描画依存を持たない。 ラベルは
htmlLabels: false(SVG<text>描画)。— CLAUDE.md規約より
htmlLabels: falseをtrueに変更すると、ビルド時の文字幅計測とページ表示時のCSS適用で差異が生じ、ラベルの末尾が切れる。この設定は変更しないこと。
テーマ設定はかなり細かくカスタマイズしている。ダークモードに合わせて全ノードの色を個別指定している。
import type { MermaidConfig } from "mermaid";
export const mermaidConfig: MermaidConfig = {
theme: "dark",
securityLevel: "strict",
htmlLabels: false,
themeVariables: {
darkMode: true,
background: "#111",
fontFamily: "Moralerspace, ui-monospace, Menlo, monospace",
fontSize: "15px",
primaryColor: "#1f1f1f",
primaryTextColor: "#ddd",
primaryBorderColor: "#ddd",
secondaryColor: "#262626",
secondaryTextColor: "#ddd",
secondaryBorderColor: "#5a5a5a",
tertiaryColor: "#181818",
tertiaryTextColor: "#ddd",
tertiaryBorderColor: "#5a5a5a",
lineColor: "#ddd",
textColor: "#ddd",
mainBkg: "#1f1f1f",
nodeBorder: "#ddd",
clusterBkg: "#181818",
clusterBorder: "#5a5a5a",
titleColor: "#ddd",
edgeLabelBackground: "#111",
},
};Mermaidのビルド時レンダリングではMoralerspaceフォントをbase64で埋め込んでいる。フォントファイルを差し替えた場合、Mermaidの文字幅計測がずれてノードの重なりが発生する可能性がある。
Webとpackagesの両方がMermaidを使うが、テーマ設定は共有せず各々で定義する。これも「Webを共通化のために歪めない」方針の一環だ。
このサイトではさまざまな種類のダイアグラムを利用できる。たとえばrehypeプラグインのクラス構造はこう表現できるし——
表示機能の使いどころを俯瞰する四象限チャートも描ける。
タイトルを省略した場合はヘッダーにmermaidとだけ表示される。
開発ワークフローとCI
開発の流れはMakefile経由で統一している。package.jsonのscriptsにはあえて定義せず、すべてのコマンドをMakefileに集約した。
CIパイプラインの順序
make format— Biomeで整形make lint— Biomeでリントmake web-typecheck— Astroの型チェックmake web-test— Bunによるテスト実行make web-build— 本番ビルド
この流れを状態遷移図で表すとこうなる。
make ciを実行するとこれらが一気に流れる。所要時間のイメージは以下の通り。
packages/*のsrc/を変更した場合、そのパッケージをworkspace:*で参照しているユニットにもversion bumpが波及する。bump漏れはmake ciで検出される。
force pushは避けること。CIの比較ベースが消失しfatal: bad objectでversion bumpチェックが失敗する。push前にmake ciをローカルで通して漏れを防ぐ。
CIが通ったらpushする。ブランチ戦略はシンプルにmain + featureブランチのみ。
デプロイと配信
ビルドした成果物はCloudflareにデプロイされる。
Wranglerの設定は最小限だ。
compatibility_date: "2026-05-24"
assets:
binding: ASSETS
observability:
enabled: trueスタイル設計:テーマとトークン
色やタイポグラフィはCSSカスタムプロパティ(デザイントークン)に一元化している。コンポーネント内にカラーコードを直書きすることは禁止だ。
:root {
--ink: #ddd;
--bg: #0a0a0a;
--accent: #56b4e9;
--border: #333;
--code-bg: #111;
}技術スタックの比重はこのようなイメージだ。
ローカル環境のセットアップ
このリポジトリをクローンして開発を始めるには、まずNode.js(もしくはBun)が必要になる。
- 公式サイトからインストーラをダウンロード
.msiを実行してウィザードに従う- PowerShellで確認:
node --version
npm --versionHomebrewでインストール:
brew install nodeバージョン確認:
node --version
npm --versionパッケージマネージャーでインストール:
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejsバージョン確認:
node --version
npm --versionAstroのインストール
Node.jsが入ったら、Astroをインストールする。パッケージマネージャーはお好みで。
npm install astroyarn add astropnpm add astrobun add astroTypeScriptとJavaScriptのどちらでも書けるが、このプロジェクトではTypeScriptを使っている。
const greet = (name: string): string => {
return `Hello, ${name}!`;
};const greet = (name) => {
return `Hello, ${name}!`;
};package.jsonはこんな具合だ。
{
"name": "@ratete/web",
"version": "0.1.16",
"private": true,
"type": "module"
}開発サーバーの起動はMakefile経由で行う。
make ci # format-check → lint → typecheck → test → build言語指定なしのコードブロックも書ける。
これは言語指定なしのコードブロック。
プレーンテキストとして表示される。開発環境と本番環境の違い
ローカルと本番では設定が異なる。それぞれの特徴を見てみよう。
開発サーバー設定
- ホットリロード有効
- デバッグログ出力
- ソースマップ生成
デプロイ設定
- ミニファイ有効
- ログレベル: errorのみ
- CDN配信
参考にしたリソース
このサイトを作るにあたって特に参考になったのは、Astroの公式ドキュメントだ。
Mermaidはダイアグラム記法のデファクトスタンダードと言ってよい存在で、GitHubでも非常に活発に開発が続いている。
SNSでの反応
リリース後、いくつかの反応をいただいた。
@astrodotbuildに返信動画付きの投稿もあった。
画像を複数枚含む投稿も。
@OpenAIに返信

まとめ
ratete.devはビルド時にすべてを解決するという思想で設計されている。MermaidのSVG事前描画、OGPカードのビルド時フェッチ、Shikiによるサーバーサイドシンタックスハイライト——いずれもクライアントにJavaScriptを送らないための工夫だ。
当初はクライアントサイドでMermaidを描画していたが、パフォーマンスとセキュリティの両面からビルド時描画に移行した。太字で強調すべきは、この判断によってLighthouseのパフォーマンススコアが大幅に改善したことだ。rehypeプラグインを自作する手間はかかったが、結果的には正しい選択だったと思う。
日本語テキストの中にcodeを挟んだ場合の行高への影響や、複数のインラインコードが連続する場合でも読みやすく表示できるよう、CSSを調整している。
この記事自体がAstro公式ドキュメントにあるようなMDX記法で書かれており、表示されているすべての要素——テーブル、コードブロック、Callout、Mermaidダイアグラム、URLカード、ツイートカード、CodeGroup、Tabs——がこのリポジトリの表示エンジンによって描画されている。
以下に、この記事で利用した全表示機能を一覧にしておく。
| カテゴリ | 機能 | バリエーション |
|---|---|---|
| Markdown | 見出し | h2 / h3 / h4 |
| Markdown | リスト | 順序なし / 順序付き / ネスト |
| Markdown | テキスト装飾 | 太字 / 斜体 / 取り消し線 / インラインコード |
| Markdown | 引用 | blockquote |
| Markdown | 水平線 | --- |
| Markdown | リンク | インラインリンク |
| テーブル | 基本 | 2列 / 多列(横スクロール) |
| コードブロック | 表示 | 短い(通常) / 長い(折りたたみ) |
| コードブロック | 言語 | TypeScript / JSON / Bash / CSS / YAML / なし |
| Callout | タイプ | note / warning / caution |
| Callout | ラベル | デフォルト / カスタム |
| URLカード | OGP | ベアURL → リッチカード |
| ツイートカード | 埋め込み | テキスト / 投票 / リンク付き / 動画 / 縦動画 / 複数画像 |
| Mermaid | flowchart | LR / TD / サブグラフ / タイトルなし |
| Mermaid | sequenceDiagram | autonumber / Note |
| Mermaid | erDiagram | エンティティ関連 |
| Mermaid | stateDiagram-v2 | 状態遷移 |
| Mermaid | pie | showData付き |
| Mermaid | classDiagram | クラス継承 |
| Mermaid | gantt | タイムライン |
| Mermaid | gitGraph | ブランチ・マージ |
| Mermaid | block-beta | ブロック構成図 |
| Mermaid | quadrantChart | 四象限チャート |
| コードグループ | タブ切替 | 2タブ / 4タブ(パッケージマネージャー) |
| コンテンツタブ | タブ切替 | 2タブ / 3タブ(OS別手順) |