サイトを大改造した話——ダーク海洋テーマからブログ機能フル装備まで
TOPページをジンベエザメの深海テーマに刷新し、タグ・検索・読了時間・OGP自動生成など10以上のブログ機能をClaude Codeで一気に実装した記録
登場人物
- リナ社長 … 高校生なのに会社経営するやり手ギャル。テックにも強くてAI活用が得意。
- タクヤ … 入社3年目の男性社員。真面目で少しだけコードが書ける。
きっかけ——「なんか地味じゃない?」
タクヤがいつものようにPCに向かっていると、リナ社長がモニターを覗き込んできた。
リナ社長「ねえタクヤ、jinbei-labのTOPページってさ、なんか地味じゃない?ジンベエザメのサイトなのに全然ジンベエザメ感ないし」
タクヤ「デフォルトのAstroテーマそのままですからね……確かに」
リナ社長「やばくない?せっかく jinbei-lab って名前なのにさ。深海感とか、実験所っぽい雰囲気とか、もっと出せるじゃん」
そこからリナ社長の「サイト大改造計画」が始まった。
TOPページをジンベエザメの深海へ
まずTOPページのデザインを根本から変えた。キーワードは「深海の実験所」。
- 背景色を
#061325(深い海軍色)に - ジンベエザメの画像(Geminiで生成)をヒーローに配置
- 青緑の発光色(
#00d4aa)をアクセントカラーに - 気泡が浮かぶCSSアニメーション
リナ社長「わ、めっちゃいい!本物の実験所みたい!」
タクヤ「気泡アニメーションがじわじわ動いてて、確かに深海感ありますね」
ポイントはCSSのスコープ管理。他のページに影響が出ないよう、body.lab-home-page というクラスで完全に分離している。ブログ一覧(.blog-list-page)や記事ページ(.blog-post-page)も同じ作戦で、ページごとにテーマカラーを持たせた。
ブログ一覧・記事ページも全面リニューアル
TOPが良くなったら、ブログ周りも気になってきた。
リナ社長「ブログ一覧、どの記事がどれかわかりにくいんだけど。カードにもっと情報入れてよ」
リナ社長のひとことで、ブログ周りの改修リストが膨らんだ。
追加した機能一覧
| 機能 | 内容 |
|---|---|
| 日付降順ソート | 新しい記事が上に来るように |
| 読了時間 | 一覧カードと記事ページ両方に表示 |
| タグ表示 | 一覧カードにタグピルを表示 |
| サブ記事の差別化 | sub タグ付きはオレンジ系に変色 |
| 検索バー | タイトル・説明・タグをリアルタイム検索 |
| カレンダー | サイドバーに表示、日付で絞り込み |
| 前後記事ナビ | 記事下部に前の記事・次の記事ボタン |
| OGP画像自動生成 | SNSシェア時にタイトル・タグ入りの画像を表示 |
| コメント機能 | Giscus(GitHub Discussions連携)で導入 |
リナ社長「これだけ詰め込んだのに1日で終わったじゃん。Claude Codeすごくない?」
タクヤ「コード量は結構多かったんですが、設計を指示すれば実装してくれるので……確かに早かったです」
読了時間の計算ロジック
タクヤ「読了時間ってどうやって計算してるの?」
リナ社長「日本語と英語で読む速さが違うから、それぞれ別に計算してるよ」
export function calcReadingTime(body: string): number {
const japanese = (body.match(/[\u3000-\u303F\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]/g) || []).length;
const english = (body.replace(/[\u3000-\u303F\u3040-\u30FF\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]/g, ' ')
.match(/\b[a-zA-Z0-9]+\b/g) || []).length;
return Math.max(1, Math.ceil(japanese / 400 + english / 200));
}
日本語は400文字/分、英語は200単語/分で計算。タクヤがこの記事の読了時間を確認したら「約3分」と出た。
タクヤ「ちゃんと機能してる」
OGP画像でつまずいた話
ここが一番ハマった。
OGP画像(SNSシェア時に表示されるサムネ)を自動生成するため、Satoriというライブラリを使った。最初は src/pages/og/[slug].png.ts というAstroのAPIエンドポイントとして実装したのだが……。
リナ社長「Cloudflareでビルドしたら Error: No such module "node:fs" って出たんだけど」
ローカルでは動くのに、Cloudflare Workersのランタイムには node:fs がない。フォントファイルの読み込みに使っていたのが原因だ。
解決策:Astroページとして書くのをやめて、ビルド前に実行するNode.jsスクリプトに切り出した。
"build": "node scripts/generate-og.mjs && astro build"
scripts/generate-og.mjs がビルド前にMarkdownを全部読み込んでPNGを生成、public/og/ に保存。Astroはそれを静的ファイルとして配信するだけ。Node.jsコンテキストで動かすのでWorkers制約に引っかからない。
タクヤ「分離すれば良かったんだ」
予約投稿もできるようになった
リナ社長「この記事、明日の9時に公開したいんだけど、自動でできる?」
できる。静的サイトでの予約投稿は:
pubDateを未来の日時に設定してコミット- ブログ一覧・記事生成時に「現在より未来のpubDateは表示しない」フィルターを追加
- GitHub Actionsで毎日9時(JST)に空コミット→pushしてCloudflare Pagesの再ビルドをトリガー
on:
schedule:
- cron: '0 0 * * *' # 毎日 0:00 UTC = 9:00 JST
ビルド時刻が pubDate を超えた瞬間から記事が公開される仕組み。完全無料(GitHub Actionsはパブリックリポジトリ無料、Cloudflare Pagesも無料枠内)。
リナ社長「無料でここまでできるの、ふつうにすごいわ」
まとめ
今回の改修でやったこと:
- デザイン刷新: 深海・実験所テーマでブランドらしさを出した
- ブログ機能: タグ・検索・読了時間・OGP・前後ナビなどを一気に実装
- インフラ: Cloudflare Workersの制約を理解した上でビルドフローを設計
- 予約投稿: GitHub Actions × Cloudflare Pagesで完全自動化
リナ社長「全部Claude Codeにやらせたじゃん」
タクヤ「指示出しと確認はしてますよ……」
リナ社長「それがエンジニアリングだよ」
コメント