背景
自分の会社サイトに、単機能のWebツールを少しずつ足しています。
今日で91個になりました。ブラウザだけで完結する小さいやつばかりです。今日足したのはCSS単位変換(px と rem と em と pt を相互に換算するもの)と、HTTPステータスコードの一覧検索の2つ…。
数が増えてきて困っていたのが、ツールを1つ足すときに触る場所の多さでした。
ツール本体のページを作るのはいいとして、そのあとに一覧ページのカード、トップページのカード、ItemList の構造化データ、HTMLサイトマップのリンク、XMLサイトマップ、と5〜6か所を手で直す必要がありました。しかも日本語版と英語版の両方。
当然のように、どこかを忘れます…。
台帳から生成するようにした
なので、一覧まわりは全部 data/tools.json という台帳から生成するようにしてあります。1件はこういう形です。
{
"slug": "cssunit",
"cats": ["convert"],
"categoryV2": "calc",
"iconAria": false,
"added": "2026-08-02",
"ja": {
"name": "CSS単位変換",
"desc": "px・rem・em・pt・% を相互換算。ルートと親のフォントサイズを指定でき、rem と em の違いも確認できる。",
"icon": "CSS"
},
"en": {
"name": "CSS Unit Converter",
"desc": "Convert px, rem, em, pt and % with configurable root and parent font sizes, so the rem/em difference is visible.",
"icon": "CSS"
}
}
cats は一覧の絞り込みボタンに使う分類、ja / en はそれぞれの言語で出す名前と説明です。この1件を足して python3 ops/gen_tools.py を走らせると、各HTMLの <!-- GEN:xxx START --> と <!-- GEN:xxx END --> に挟まれた部分だけが書き換わります。
該当のHTMLを直接編集するのは禁止、というルールにしています。手で書ける状態を残しておくと、いつか手で書いてしまうので…。
なお、ツール本体のページ(public/tools/<slug>/index.html)は生成対象外です。あれは検索から人が来る資産なので、生成器には触らせない方針にしています。
生成器に怒られた
今日、台帳に2件足して生成器を走らせたら、こう言われました。
$ python3 ops/gen_tools.py
台帳検査 NG:
- sitemap.xml に無い: https://hashitosystem.com/tools/cssunit/
- sitemap.xml に無い: https://hashitosystem.com/en/tools/cssunit/
- sitemap.xml に無い: https://hashitosystem.com/tools/httpstatus/
- sitemap.xml に無い: https://hashitosystem.com/en/tools/httpstatus/
sitemap.xml は生成器が作っているファイルではありません。歴史的な事情で別管理になっていて、追記は手作業です。
つまり生成器は「自分が作っていないファイルとの整合」を検査して落ちてくれた、ということになります。
これがけっこう効きました。自分が忘れていたのはまさにここで、もしこの検査が無かったら、一覧には出るのに sitemap には無いページが4つできていたはずです。しかも見た目には何も起きないので、当分気づかない…。
生成器を作るときは、生成するものだけを見がちです。でも実際に事故るのは「生成しないけど整合が必要なもの」のほうだったりします。検査だけでも同じコマンドに載せておくと、追加手順の抜けがその場で止まります。
言われたとおり sitemap に4件足して、もう一度走らせました。
$ python3 ops/gen_tools.py
台帳: 91 ツール(うちトップ掲載 9)/ カテゴリ 8(Phase2案 10)
更新: tools/index.html, index.html, sitemap.html, en/tools/index.html, en/index.html, en/sitemap.html
日英あわせて6ファイルが更新されました。手で6か所直していた頃を思うと、だいぶ気が楽です。
そのあとOGP画像でも同じ形で怒られた
続けてSEOの監査スクリプトを流したら、また8件出ました。
$ python3 ops/seo_audit.py
SEO AUDIT: 8 issue(s)
[tool-og:image-file-missing] tools/cssunit/index.html | public/ogp/tools/cssunit.png
[tool-twitter:image-file-missing] tools/cssunit/index.html | public/ogp/tools/cssunit.png
...
ページの <meta property="og:image"> は書いたけれど、その画像ファイル自体を作っていませんでした。OGP画像というのは、SNSにURLを貼ったときに出るサムネイルのことです。
これも専用のスクリプトがあって、足りないぶんだけ生成してくれます。
$ python3 ops/gen_tool_ogp.py
generated /Users/hashito/git/web/hashitosystem/public/ogp/tools/httpstatus.png
generated /Users/hashito/git/web/hashitosystem/public/ogp/tools/cssunit.png
done: 2 missing OGP image(s) generated
$ python3 ops/seo_audit.py
SEO AUDIT: OK (問題なし)
やっていることは「メタタグに書いたパスに実ファイルがあるか」の確認だけなのですが、これも人間には見えない類の欠落です。壊れたOGPは、SNSに貼るまで気づけないので…。
作ったツールが本当に動くかは別途見る
生成と検査が通っても、ツール自体が動く保証はありません。ここは別で確認しました。
デプロイしてから、公開されたページのJSを実際に叩いて換算結果を読んでいます。CSS単位変換のほうはこうなりました。
16px(root 16 / parent 16) → rem 1 / pt 12 / pc 1 / % 100
root を 10 に変更 → rem 1.6
parent を 20 に変更 → em 0.8 / % 80
1in を入力 → px 96 / cm 2.54 / pt 72
CSSの仕様では 1in = 96px と決まっていて、1pt = 1/72in なので 1pt = 96/72 ≒ 1.3333px、1pc = 1/6in = 16px になります。上の出力はその定義どおりでした。
HTTPステータスコードのほうは、404 で絞ると 404 と 410 の2件が出て(410 の説明文に「404 と違い」と書いてあるため)、区分を5xxにすると 500/501/502/503/504 の5件になる、という挙動を確認しました。
生成器が通ったことと、ツールが正しく動くことは別の話です。前者は形式、後者は中身なので、どちらも見ないと出せません。
台帳から生成する仕組みを入れるなら、生成しない周辺ファイルとの整合検査を同じコマンドに載せておくといいです。今日それに2回助けられました。
本記事はAI補助で執筆した、個人開発の紹介記事です。