H1タグの複数使用とページタイトルへの適用可否について軽く調べた

前回のジャンル::調査の記事(2025/05/14執筆)から一年半ほど、久々の調査記事だ。今回は前々から気になっていたが調べてこなかったことについて調べたので、そのことを書いてゆく。

一個前の記事にもあるとおり、ここ最近CMSの開発を続けていて、どうしても記事本文の見出しレベルを上げて使えるHタグを一つでも増やしたい思いが募っていたので、これの実現性について調べることにした。

先に断っておくと私はWebアクセシビリティの専門家ではないし、単なる素人だ。よってこの記事に書いてあることはすべて素人判断である。

考え始めたきっかけ

現在このサイトでは記事本文中に書いた見出し構文(#)はH3から始まることになっており、H2から始めたい思いがあった。これは単に使える見出しの深さが増えるからだ。HタグはH6までしかないので、使えるHタグの数が増えるのは嬉しいことだった。

Hタグが足りない問題はLyndbaumより前、adiaryよりもっと前、WordPressを使っていたころから、ずっと思っていた。これは私がやたら長文を書いてしまったり文章構成がへたくそな部分も大いにあると思うが、深いHタグが欲しくなるケースが多々あったのだ。今でもそうだ。仕事で使っていたDocBaseに対して使えるHeadingの階層を増やしてほしいと要望を出したこともあるくらいだ。

私は見出しに対して非常に執着心があり、5年前にはRedmineの見出し目次を追従させるuser.jsを書いてたほどで、その魂は見出し追従機能を持つLyndbaumの標準テーマにも引き継がれている。

それゆえに使える見出しの階層を増やすことができないかというのは長年の悩みだった。

記事タイトルをH1にすることで、記事内のHを一段階上げることができる対比図

もし左の構造を右の構造に転換できればH3始まりをH2に始まりにできるわけだ。

ただ、この案には一つ引っかかる点があった。記事ページだけを見れば、H1は記事タイトルの一つだけになるので何の問題もない。しかしこのサイトは一覧ページに記事全文が並ぶ古典的なレイアウトなので、一覧ページでは記事の数だけH1が並ぶことになる。私の理解ではH1は1ページに一つ、ページタイトルに対して使うものであり、これを愚直にやってしまうとHTMLセマンティクスを壊してしまう懸念があった。

しかし、私は同時に「サイトの中にH1を複数配置することは問題ない」という相反する情報も持っていた。いつ見聞した情報かは全く記憶にないが、頭の中にあった情報だ。

そこでまず、この情報の真偽を調べることにした。

H1タグは何であり、どう使うことができるのか?

こういう時に頼りになるのはMDNだと思って私はすぐに<h1>–<h6> セクション見出し要素 - HTML | MDNを開いてみたが、核心的な情報はなかった。

<h1>~<h6>はHTMLの要素で、セクションの見出しを6段階で表します。<h1>が最上位で、<h6>が最下位です。

MDNに書かれていたH1、もといHタグの説明は大まかにはこの一行だった。あとは精々次のような注意書きがあっただけである。

1つのページに複数の<h1>要素を使用しない

1つのページに複数の<h1>要素を使用することはHTML標準では認められていますが(入れ子でない限り)、これはよい習慣とはみなされません。

つまりH1とは見出しである。1つのページに複数の<h1>要素を使用することは、HTML標準では認められていても、よい習慣ではないと書いてあるだけだ。何が良くないのかは全く書かれていない。

ひとまず私は1つのページにH1を複数配置することは問題になる可能性があるということを覚えた。

世間がHタグをどのように使っているか調べてみた

MDNに具体的な説明がないので困った私は適当に思いついたサイトをピックアップして、Hタグがどのように使われているかを簡単に調べてみることにした。すると面白い発見があった。

20個のサイトでHタグの使用状況を調べた

調べたサイトの数が20個なのは、これ以上良いサイトがぱっと出てこなかったからである。部分的な項目についてはこの表にないサイトも見ている。

サイトのリンク先には不適切なものも含まれるため注意。リンクは後から足したため、確認したページと異なる可能性がある。参考程度の情報。また1サイト数分で見ている*1ため内容には誤りがある可能性がある。

サイト サイトタイトル 一覧の記事タイトル 一覧に記事全文が出る 記事ページタイトル 記事の開始H 備考
Daring Fireball Hなし H1 〇 H1 H2 H1がたくさんある。Markdownの始祖だが...
るるせなーれ Hなし H1 〇 H1 H2 WordPress theme EASEL
はてなブログ H1 H1 〇 H1 H1 任意のブログを何個かピックアップし、自分のアカウントでも確認
Serene Bach v4 H1 H2 〇 H2 H1 セルフホストして初期状態で確認
caniuse Hなし H3 〇 H3 H4 H2はフッターの一要素にある
Reddit Hなし Hなし × H1 H1 記事ページにはH1を複数作れて、実際に作られている
神戸新聞 H1 Hなし × H1 - 記事ページにはH1が二つある。記事本文中に見出しがない
MDN Hなし Hなし × H1 H2
ci-en Hなし H2 × H1 H2 ページによってはH2の中にH1があるので余り参考にならない(恐らく実装的にページコンポーネントをH2があるコンポーネントの中にincludeしている
Yahoo!ニュース H1 Hなし × H1 H2 記事ページにはH1が二つある
NovelAI Journal Hなし H2 × H1 H3 不自然にH3になっていることから、恐らく原文は見出しを##で書いていると思われる
Storybook Blog Hなし H2 × H1 H2
ICS MEDIA H1 H2 × H2 H3 一覧ページにはサイトタイトルと「一覧」中タイトルの二箇所にある
hacktk.net Hなし Hなし × H1 H2
ジブンノート Hなし Hなし × H1 H2 中タイトル方式
さくマガ Hなし H2 × H1 H2
uga.dev H1 Hなし × H1 H2 記事ページにはH1でサイトタイトルが出ない
p-hone.info Hなし Hなし × H1 H2
PukiWiki H1 一覧機能がない 一覧機能がない Hなし H2
SSP ヘルプ Hなし 一覧機能がない 一覧機能がない H1 H2

*1 : AIとかなしの完全目検です。

20個のサイトで確認した、使用割合の結果

調べた結果、次のことが分かった。

指標 実数 割合
総数 20 100.00%
記事本文の最初のHタグがH2 13 65.00%
記事ページで記事タイトルがH1 16 80.00%
一覧に記事全文 5 25.00%
サイトタイトルにHタグがない 13 65.00%
一覧の記事タイトルがH1 3 15.00%

つまり過半数のサイトでサイトタイトルにH1が使われていないし、過半数のサイトで記事ページで記事タイトルがH1だし、過半数のサイトは記事の最初に出てくるHタグがH2なのだ。

これは記事ページに限って言えば、私がやろうとしている構成は多数派に属するということを意味する。残る論点は、全文が並ぶ一覧ページでH1が複数になることだけだ。

その一覧ページについては、一覧の記事タイトルをH1にしているサイトは2つしかなかった。ただし、一覧に記事全文を出しているサイトが20サイト中5サイトしかないので、そもそもの母数が小さい。

この少ない母数に入るサイトの中でDaring Fireballとるるせなーれは、私にとってはかなり理想形だった。何故なら、サイトタイトルがHなし、一覧と記事ページの記事タイトルがH1で、本文中に出現する最初のHタグはH2だったからだ。これがあるべき姿なのかどうかは議論の余地がありそうだが、運命みたいなものは感じた。だって私がやろうとしてるのはサイトのH階層を浅くして、MarkdownパーサーのH階層を一段上げることだからだ。

もう一つがはてなブログだ。個人的にはSEOに強い印象がなく、かつて使っていた限りでは、ろくにインデックスされた記憶もない。しかし、はてなブログはこの形式でも一応インデックス自体はされているので、つまり検索エンジンのクローラーが読んでくれる可能性があるということだ。あとは評価がどうなるかという部分にはなってくる。

サイトタイトルについていえばGitHubもBulmaやMaterial UIの公式サイトもいわゆるヒーローバナーのタイトルにH1は入っていない。つまりここは削れる公算が大きい。そもそも<title>に書いてあることと同じ文字列にH1が必要かどうかは一つ疑問でもある。

H1タグが1ページに複数あるサイトは検索エンジンからどう評価されるのか?

Google公式SEOスターターガイド | Google検索セントラルによると、Googleはセマンティクスをそもそも評価していないらしい。それも見出しに焦点を当てて説明がされているほどにだ。

見出しの数や順序
見出しを意味的な順番にすることは、スクリーンリーダーにとっては素晴らしいことですが、Google検索にとっては順番どおりに使われていなくても問題ありません。ウェブは一般に正しくないHTMLで書かれています。そのため、Google検索がHTML規格に潜むセマンティックスの意味に依存することはめったにありません。

また、ページごとに魔法の見出し数や理想的な見出し数といったものが存在することもありません。ただ、リンクの数が多すぎると感じる場合、実際にそうである可能性が高いと言えます。

つまり見出しの意味はGoogleにとって無価値な可能性があり、ページにH1が複数あっても、SEO上はほとんど問題にならないかもしれない。

アクセシビリティ上はどうか?

Googleの説明にもある通り、見出しの構造が一番有用になってくるのはスクリーンリーダーだと考えている。なので本来ここが一番大事な論点なのだが、正直なところ私には確かなことが言えない。私はスクリーンリーダーのユーザーではないので、自分で良し悪しを判断できない。そして調べても確定的な情報を得ることはできなかった。

私の認識ではHタグはスクリーンリーダー向け以外にはほとんど役に立っておらず、目が見えていれば、かなりどうでもいいものだと思う。何故なら文字を大きくする目的だけでHタグが使われるのはよくあることだからだ。つまり誰も気にしてなんかいない。

何よりモダンなブログのように記事一覧がタイトルとようやくで並んでない古典的レイアウトのサイトで考えることではないだろうし、ひとまずH1の多用がHTMLとしてVALIDなのであれば、それはもう必要以上に気にしても仕方がないような気がした。

出来る範囲で配慮したほうがいいとは思うが、WCAGを守ることは義務ではない。インターネットは自由だ。映画館やホール、スタジアムの座席がすべて車いす対応でないように、K2やエベレスト、富士山に車いすスロープがないように、アクセシビリティは必ず何もかもすべてが満たす義務はない。大抵の家には階段しかなく、エレベーターはない。

しかしソフトウェアの開発者であれば守るべきかもしれない。しかしはてなブログは守っていない。どこまで拘るか、これは決めの問題でしかない。ならば、私はH2を本文で使いたい、そう思えた。

WCAGは何といっているか?

軽く見た範囲では何も言ってなさそうだった。全部は見てない。

少なくともH42: 見出しを特定するために、h1 要素~ h6 要素を使用するやF43: 達成基準 1.3.1 の失敗例 - コンテンツにおける関係を表さない方法で、構造的なマークアップを使用しているには1ページに複数のH1を書くなとはどこにもない。階層を守れ、飛ばすなくらいしか書かれていない。

LLMは何と言っているか?

Claude Opus 5.5によると記事一覧の記事タイトルがH1であることはスクリーンリーダー利用者によって記事タイトルだけを飛ばし読みする場合に有益な場合があると言っていた。本当かどうかは知らない。何故なら私はスクリーンリーダー使用者ではないからだ。

なら自分がスクリーンリーダーを使ってみればいいのでは?

もちろん、私もスクリーンリーダーを導入すれば使うこと自体はできる。ただその使い方が視覚障害者と同じであるかどうかは、私にはわからない。これは聾者とコミュニケーションをとるときに筆談すれば伝わりやすいという誤謬と同じだ。

筆談ができる聾者は少なくないが、得意であるとは限らない。書き言葉は音声言語を土台にしており、言葉は本来、耳から聞いた情報をもとに身につくものだからだ。つまり筆談は、もともと聴覚を持っている人を前提とした手段であり、先天的な聾者には必ずしも当てはまらないとされている。この点は「特集/聴覚障害者のコミュニケーション ろう者コミュニケーションの諸問題」などに詳しい。

同様に、晴眼者がスクリーンリーダーを使ったところで、視覚障害者と同等の経験が得られるかといえば、私は疑問に思う。理解できたとしても、それはあくまで晴眼者の範疇での理解にとどまるだろう。

はてなブログを利用している視覚障害者は存在するのか?

手っ取り早い方法としてH1まみれのはてなブログを利用している視覚障害者がもし存在すれば、それはつまりスクリーンリーダー的に問題がない可能性があるというので調べてみた。すると利用者を確認することができた。

neputa noteというブログははてなブログで作られており、ロービジョンの方が運営されているとある。DOM構造もサイトタイトルがH1、一覧の記事タイトルがH1、記事本文の最小見出しはH2だ。

もちろん、このサイトの存在を以って直ちに問題ないと判断することはできない。何故なら失礼な話、このサイトの運営者がロービジョンであることを確かめる術が私にはないからだ。とはいえ、妙な広告なども見られないので書いてあることは正しいようにも見える。

結論

サイトタイトルはHなし、記事タイトルはH1、本文中の最小見出しはH2にすることで私の理想が叶うのではないかと思った。恐らくSEOには大きく影響せず、ある程度のマシンリーダブル性も担保できるのではないかと考えた。

もちろん確たる根拠も何もない。あくまで今回の簡易な調査で分かったことでしかないし、アクセシビリティの専門家は異を唱えるかもしれない。しかし、これは結局のところ決めの問題だ。Lyndbaumの開発者たる私がそう決めたら終わる話なのだ。

最終的な構成案の対比図

というわけで次回のバージョンでMarkdownパーサーとテンプレートに手を入れ、サイトタイトルをHなし、記事タイトルをH1、本文の最小見出しをH2にしようと思うのであった。

Lyndbaum v4.44アップデート

adiary置き換え計画始動から連なる、Lyndbaum v4.43アップデートの続き。怒涛の一日二回更新。なんか前にも見た気がするな…?

v4.43は二日がかりで実装したが、今回は半日くらいなのでまぁ…。基本的にはv4.43で出たバグ対応が中心だが、ついでに機能改善もしている。

バグ対応

lyndbaumテーマで、サイドバーを開くボタンの位置がv4.43の変更で壊れていたのを修正

2カラムテーマを追加した影響で2カラムテーマ側の位置オフセットが3カラムテーマ側にもかかっており、二重にオフセットされていた関係でずれていたのを修正した。

編集画面で本文に((を入力するとフリーズする問題を修正

正規表現の書き方が悪く((の後に50文字あると処理が数年終わらない次元になっていた。ReDoS的なやつ。正規表現を直して対応した。

Markdown記法ヘルパーの見出しボタンでシャープの後ろに半角スペースが入らない問題を対応

adiary時代からある不具合。手打ちしたほうが早いため使うことはないのだが、画面上存在しており、消すのも微妙な存在なので対応した。

H2, H3...みたいにレベルを増やすのは画面にボタンが増えすぎる、セレクトボックスにすると邪魔などの問題で対応しなかった。

本文を検索できない問題の対応+αで検索機能の強化

Lyndbaum v4.42アップデートで入れた長文だとIMEがまともに動かない問題の対策の副作用で、Ctrl+Fを使った本文の検索ができなくなっていたため、これを出来るようにした。

具体的にはCodeMirrorは表示領域以外を隠すことで軽量化しているため、CodeMirror組み込みの検索機能を入れて対応した。

標準でわけわからんほど高機能なCodeMirrorの検索機能

標準でかなり豪華な検索機能だったので、非常に優秀な検索機能と、更に置換機能までもが手に入った。エディタにフォーカスがあるときにCtrl+FやF3すると起動するため、使い勝手は結構いい。

機能改善

なんか追加とか変更ってよくわからないから改善でまとめたほうがいいんじゃないかなとか…。

記法ヘルパーのボタンで出る入力のダイアログを開いたら、最初の入力欄にフォーカスするようにした

変更前 変更後
入力欄にフォーカスがなかった 入力欄にフォーカスが入るようにした

変更前は入力欄にフォーカスがかかっておらず、わざわざクリックする必要があったが、開いたときに自動で最初の入力要素にフォーカスするようにした。

<div>実装だったためついでに<dialog>にしようと考えたが、Opus 5.5先生に<dialog>は最も最初の要素にTabIndexが貼られ、閉じるボタンにフォーカスが入るのでハンドリングコストが重いと告げられ考えるのをやめた。

記法ヘルパーのリンクで、最初から入っているhttp://をhttps://にした

いまどきhttp://は零細企業か何も考えてないか妙なこだわりのある個人サイトや、放置サイトくらいでしか存在しないと思うので標準でhttps://に変えた。

ブランクにしたり、https://+全選択でコピペ上書できるような配慮も考えたが、手打ちしたほうが早くなるようにする機能を入れたため、凝った作りにはせず単にプレースホルダの初期値だけを変えた。

編集画面でMarkdownモードの時に入力補助機能を追加

簡単なMarkdownエディタレベルの入力補助を入れているので大分快適だと思う。

Markdownモード以外では動かないと思うが確認はしていない。ここは私がすべて手動で動作確認している。

以降の説明では¦の部分にキャレットが入る想定で書いている。

ショートカットキー

文字列選択時は選択した文字列を記法で囲み、文字列選択状態となり、キャレットは出ない(←→を押すと選択文字列の始端ないし終端に移動する)

Key Syntax
Ctrl+B **¦**
Ctrl+I *¦*
Ctrl+K [¦]()

取り消し線のショートカットキーは作っていない。これはCtrl+DもCtrl+Sもブラウザのキーバインドに干渉するためだ。まぁ作る作らない以前にCodeMirrorの標準機能をそのまま使っているだけだが…w

文字列を選択してURLペースト

[文字列](ペーストしたURL)¦

相対パスや記事IDなど、URL形式でないものは手動で入力する必要がある

自動カッコ入力

かっこを入力すると対のかっこが入る

  • (→(¦)
  • ((→((¦))
  • [→[¦]

<(「のようにMarkdownに存在しないものは対象外。

バックスペースを使ってかっこの内側からかっこを削除すると対になったかっこも消える。消したくない場合はかっこの外側から消せば片方だけが残る。

リストぶら下げ

バレットリストや数字リストを打つとエンター時にぶら下がる。

数字リストの自動調整

初期状態

1. a
2. a¦
3. a
4. a

バックスペースを一度押下

1. a
2. ¦
3. a
4. a

バックスペースを一度押下(. のスペースが消える、後続の項番が自動調整される)

1. a
  ¦
2. a
3. a

リストぶら下げ文字列入力一行目(後続の項番が自動調整されない)

1. a
  b¦
2. a
3. a

リストぶら下げ改行入力二行目(オートインデントされ、後続の項番が自動調整されない)

1. a
  b
  ¦
2. a
3. a

リストぶら下げ文字列入力二行目(後続の項番が自動調整されない)

1. a
  b
  b¦
2. a
3. a

リストぶら下げ改行入力三行目(オートインデントされ、後続の項番が自動調整されない)

1. a
  b
  b
  ¦
2. a
3. a

リストぶら下げ文字列入力三行目(後続の項番が自動調整されない)

1. a
  b
  b
  b¦
2. a
3. a

システムチェックに、OSの名前とバージョンを出すようにした

このシステムを使ってバグ報告を上げてくる人がどれほどいるかはわからないがDebianとRHELではアドバイスすることも変わるので出すことにした。UbuntuとFreeBSDでは表示できることを確認している。

Busyboxで出るかどうかは環境構築が面倒なので確認していない。Busyboxで動かないって報告が来たら考えるかも…。

既知の不具合

Android Edge或いはChromeで編集画面の記事本文を全選択した後にコピーなどの操作ができない

CodeMirrorはどうもスマホ対応が微妙らしく、掲題の環境で編集画面の記事本文を全選択するまではいけるのだが、ここでコンテキストメニューが切れてしまい、次の操作に繋げることができない状態になっている。

ここをどうにかするにはCodeMirror本体を改造するか、同等機能を持ったライブラリを自作する以外に道はなさそうだ…。

あとがき

はてさて、わずか半日でv4.43リリースから7時間で、アップデート記事の見出し行数で、15行も変更を重ねてしまう、謎バージョンが爆誕してしまった。

Ver リリース記事の
変更関連見出し行数
v4.43 40
v4.34 30
v4.17 23
v4.19 19
v4.44 15
v4.26 15
v4.22 14
v4.36 9
v4.20 6
v4.37 6
v4.42 5
v4.39 4
v4.40 3
v4.35 1

前回、v4.43のあとがきに本当に落ち着かせていきたいとか書いたのはいったい何だったのか…という感じだが、今回はあくまでバグ対応してたらなんかわきから生えてきた感じなので、今度こそは落ち着くんじゃないかな!?

GitHubにはまだパンドラの箱が眠っているのかもしれない…

GitHubのIssueにはまだなんか不穏な爆弾が見えるが、破壊的変更は多分起きないので、のんびりやれる奴だと思う。恐らく、きっと。

というか私はLyndbaum開発以外にもMastodonの改造や、Lyndbaumをベースにしたアクセス解析(それは何?)だったり、作りたいものが他にもあるし、何よりリアルが割と火の車というか、ヤバな状況なので、そっちも片をつけないといけないとかで、こればっかりに注力しているわけにもいかないという事情があるので、そろそろ本当に落ち着かせていかないといけないのである。

というかブログを快適に書くために開発しているのに肝心の更新がリリース情報ばかりというのも何ともである。いや、リリース情報はすごく書きやすいんだけど!

あとはちょっとまだまとめ切れていないが、Lyndbaumの開発で、なぜこうなっているのかみたいな知見が色々得られたのは非常にいいことだった。

例えばPrism.jsがやってることをサーバーサイドでやるとJS読み込みやCLSはなくなるけど、その分クソデカHTMLが降ってくるから総合的に見るとよくないとか、CSSの@import()はCSSが読まれた後に追加でHTTPリクエストを飛ばすのでPageSpeed Insightsのパフォーマンススコアに影響するとか、他にもレンタルサーバーではサブドメインから攻撃ができるとか、様々な知見を溜めることができた。

コミットメッセージやらIssue、PRの本文、仕様書や、このブログの過去ログとかその辺にこういった情報をちりばめてきた気がするので、そのうち気力と時間と機会があれば、それらをまとめて書いていきたいと思う。気を使わないといけない部分はかなりあるし、LLMの使い方も仕事で使うよりはるかに磨けたような気がしている。

ブログは使いやすくなるし、それどこか新システムの基盤になりそうだし、Web開発の知識は増えるし、地味に人との関わりも増えるし、Lyndbaumの開発は本当にいいことづくめである。

僅か一日強でPro換算も105%消費してしまったClaudeの使用量画面

Claudeの使用量は土曜のリセットから25時間半で21%にも達してしまい、Pro換算だと105%で超過してしまっている。とはいえ朝から2%しか伸びてないので大体v4.43のせいである。開発は起きてる間しか動かしてないのでいわゆるループエンジニアリングはしておらず、基本張り付いて動きを見てリアルタイムレビューしつつ、なんか動きがあかんかったら即停止からの、指示のやり直し、方向転換みたいなことをしている。

Lyndbaum v4.43アップデート

adiary置き換え計画始動から連なる、Lyndbaum v4.42アップデートの続き。

先週時点では、やることが減ったような気がしていたが、いろいろ思い出したので特大アップデートとなった。特大過ぎたため、後方互換性をまたもや切り捨てておりv4.41からしかアップデートできなくなっている。

実際、GitHubのPRはクソ長いし、GitHubのリリースノートも無限に長い。そしてこの記事もLyndbaum系では過去一長いと思われる。

機能追加

ブログの基本設定に「検索エンジンに掲載しない」(noindex)を追加

ローンチ前のサイトや、その手の創作サイトなどで検索除けをしたいなどのケースを考えnoindex, nofollowを公開ページのヘッダに一律で付与する設定を追加した。この状態でもRSSは出すし、pingやIndexNowも設定すれば飛ばせる。

システム管理に「ディレクトリ使用量」のタブを追加

今のサイトがどれくらい容量を食っているかが分かる画面を追加し、ユーザーが書き込む主なディレクトリの容量とファイルサイズを出すようにした

今のサイトがどれくらい容量を食っているかが分かる画面を追加し、ユーザーが書き込む主なディレクトリの容量とファイルサイズを出すようにした。

メディア管理で動画を動画以外と区別できるようにアイコンを追加

右下に小さな再生アイコンを表示している

右下に小さな再生アイコンを表示することで、動画であることが分かるようにした。静止画やテキストファイル、PDFに対して個別にアイコンをつけてると大変なので動画だけ対応した。

lyndbaumテーマに2カラム版を追加した

3カラム版しかないので2カラム版を作った。これはLyndbaumそのもののマニュアルサイトを作るのに必要だったため作成した。

そのうち色変更対応も追加していきたいが、今回はやることが多すぎて保留とした。

機能変更

JSをminifyするようにした

Lyndbaumで利用しているJSはadiaryからの引継ぎだが、基本的にminifyされていなかったのでするようにした。

添付ファイルのフォルダをpub/attachments/からpub/attach/にした

attachmentsって長くね?外部サイトとかのURLの長さ制限に引っかかりやすいよなぁ…というか引っかかったので少し縮めた。PukiWikiの添付ファイルプラグインと同じ名前。

SatsukiTimerを常に有効にした

CGIでもFCGIでも環境変数がなくとも処理時間を計測するようにした。

この対応で修正時に.pmを直すとオートリロードが壊れて500エラーになる問題が出て軽くハマった。

タグとページの設定を独立ページにした

元々はネストされた設定だったが単独の設定ページとして引き出した。

adiaryの設定画面は中々カオスなので分かりやすく整備していこう計画の一環。

ツリー操作系のUIでスマホでリネーム操作を行いやすくした

これまでツリー操作系のUIでツリーにぶら下がるノードの名前を変えようとするのは理論上ダブルタップでできたのだが、ほとんど不可能だった。

そこで、それをまともにできるように改修した。

メディア管理のDeleteキーで、フォーカスのないフォルダを消そうとしないようにし、フォーカスがあるときだけ消せるようにした

Deleteキーを何もないところで押したときにフォルダを消そうとしていたので、フォーカスがフォルダにあたっていない限り発火しないようにした。フォーカスがあるときには消えてほしいので、フォーカスが当たっているときだけ機能するようにした。

この挙動は本家adiaryにもあったと思う。

メディア管理で、フォルダをドラッグ&ドロップするとツリーが閉じるのを閉じないようにした

D&Dでツリーが閉じるのがあまりにも不便だったので閉じないようにした。

この挙動は本家adiaryにもあったと思う。

但しフォルダをD&Dしても記事に埋めているファイルパスは変わらないため、この機能は基本的に使わない機能である。個人的にはフォルダを変なところに作ったので移動したいとか、そういうケースで使う避難ハッチ機能として位置付けている。

フォルダ移動などのパス変更で全記事を書き直したり、その確認を出すと小規模な変更でも大規模な変更でも考慮することが爆発的に増えるため、現状の振る舞いを仕様とした。これはリポジトリの仕様書にも書いてあり、正式な仕様である。

デザイナのリンク集の設定画面で、リンクの行の左に並べ替えのハンドルを付けた

変更前 変更後

この設定ではリンクの順序を入れ替えられるのだが、一見するとそれがちっともわからないのでハンドルをつけてわかりやすくした。操作説明があるとより分かりやすくなると思うが、そこはやれていない。ちょっと今回やることが多すぎて、それどころではなかった。

デザイナのリンク集の設定画面で、リンクをクリックしたとき編集画面を出すようにした

元々の実装ではリンクをクリックするとリンク先に飛んでいたが、編集できたほうが嬉しいので編集できるようにした。

デザイナの記事ヘッダの日時の設定を、更新日を出すかどうかの設定に変更した

元々の設定だと投稿日か更新日しか出せず、更にページ(コンテンツ)の場合は記事の日付という謎要素まであった。個人的には両方出てほしかったし、投稿日を出さないことはあり得ないと考えていたので、投稿日は必ず出すものとし、更新日を出すかどうかに統一した。

元々の設定画面

通常記事の日時は投稿日時or更新日時で、ページ(コンテンツ)の場合は投稿日時or更新日時or記事の日付だった。

これは元々adiaryでは日付はタイトルの横に出すしきたりがあり、日付は出ているという前提があったからだと思われる?*1

*1 : つまり全く同じ日付を二箇所に出すことができた

改修後の設定画面

改修後は投稿日付が出ている前提で、投稿日付が二重に出る前提を置かず、更新日を出すか出さないかのBoolean判定とした。

ここで出す投稿日付は編集画面で設定する「日付(yyyymmdd)」であり、adiaryが裏で持っている投稿日時(tm)ではない。

設定画面で設定箇所がおかしかったもの、説明が分かりづらかったもので、見ていて気になった箇所を修正

編集画面の設定にあったサイト全体にかかわるものを基本設定に移す、設定項目がよくわからない物の名前をいい感じにするなど、細々とした対応を行った。

普段Coding Agentを使って作業していると、この辺は人間の数少ない手書きコードの出番である。指示するより書いたほうが早いからね。言うて自動テストとの兼ね合いもあるので最終的な整合はLLMにやらせたりすることもあるが…w

投稿本文の上限を既定で8MBにし、システム設定で変えられるようにした

元々のadiaryでは262,144byteがハードコードされていて、過去に倍の524,288byteに上げたのだが、これでも引っかかることがあったのでPHPの既定値*2に合わせて8MBにし、ハードコードではなく設定画面で増減できるようにした。

ぶっちゃけ絵文字やリガチャーをまぜこぜにしても4MBを超えることは現実的にないと思う。流石に一つの記事に広辞苑を突っ込めば超えるとは思うが、そもそもそれはブラウザがまともに表示できるのかとか、誰が読むのかというのは思わなくもない。

*2 : 実質的にはWordPressの既定値

名寄せ対応

コンテンツページ、コンテンツ、ページの呼び方を固定ページに揃えた

長年の歴史により呼び方がブレており、何を指しているのかが解り辛かったので統一した。ついでに記事の種別管理に使われていたフラグの物理名をwikiからpageに変更した。

ブログの呼び方をサイトに揃えた

ブログといえばブログなのだが、Wikiとしても使えるし、SatsukiSystemは強力なので、その気になれば普通のサイトを作ることも可能であることからサイトに揃えた。元から一部の呼称は「サイト」だったため、先のページの件と同じ名寄せ対応みたいなものである。

内部処理にもblogが付いたものが大量にあるが、これらは直すのがあまりにも大変すぎて旨味がないため放置している。jQuery並みの苦行になるのが目に見えており、jQueryのように排除したところで保守が楽になるわけでもないためだ。

「アルバム」の呼び方と内部の名前を「メディア管理」「media」に揃えた

アルバムをメディアに変えた時に残骸があり名前がブレ放題だったので、論理名を「メディア管理」、物理名を「media」に統一した。

機能削除

jQueryを排除した

今回最大の目玉。jQueryを葬り去った。

操作感は体感では完全に維持されている。jQueryUIとdynatreeという化け物みたいなライブラリがやってた内容を素のJSに移植した。

但しjQueryにあった$や$$のようなものは残っており、名残りみたいなものは残っている。全部変えると死んじゃうからね。

この影響でカラーピッカーをWeb標準のものに変えているため、ブラウザによって劇的に操作感が変わるようになっている。HSVのコントロールスライダーもinput type="range"で実装したため、見た目がやや変化した。

今回の対応で変化した部分

直接的にはデザイナのみが影響を受けている。間接的には他にも影響があるのだが、細かすぎてキリがないのでそちらは省略する。

カラーピッカーで現新比較ができなくなった
変更前 変更後
jQuery製の従来のピッカーには現新比較機能があった MS Edge標準のカラーピッカーには現新比較機能がない

jQuery製の従来のピッカーには現新比較機能があったが、MS Edge標準のカラーピッカーには現新比較機能がないので、これはできなくなった。そもそもカラーピッカーを使ってテーマの色を変えている人がどれだけいるのかが謎なので、ほとんど使用感への影響はないと思われる。

カラーピッカーの色選択が素直にできるようになった
変更前 変更後

jQuery製の従来のピッカーは色選択の操作をするとウィンドウが動いてしまい操作しづらかったが、Web標準のものではこれは起きなくなった。ここの操作感はよくなったと思う。

HSV選択のスライダーの見た目が変わった
変更前 変更後
変更前はシュッとした見た目 変更後は真ん丸見た目

変更前はjQuery製の従来のスライダーはシュッとした見た目だったが、変更によってWeb標準のものに変わった結果、MS Edgeでは丸っこい見た目に変化した。見た目を元通りにできるのかどうかについては調べていないが、見た目は前のほうが良かったと思う。

デザイナでウィジェットが吹き飛ばなくなった
変更前 変更後

変更前はデザイナでウィジェットを操作していると画面外まで吹き飛んでいたが、これがなくなった。何故なくなったのかは解っていないが、実際の動きを理解することが物理的に不可能なLLMがいい感じにハルシネーションしてくれて当たり前の動きに落とし込んでくれたものだと考えている。

私はLLMに指示を出すときに微妙な実装に関しては解釈の余地が残るような指示を意図的にしており、事前に用意していた「互換性を維持する」という指示を絶妙にバイパスした実装をしてくれるように祈っていたりするので、そのせいもあるかもしれない。

デザイナのプラグイン説明のウィンドウサイズを必要最低限にし、タイトルを改善した
変更前 変更後
変更前は全幅で邪魔くさかった 変更後は必要最低限の幅に落ち着いている

変更前、jQueryの時は全幅で邪魔くさかったが、変更後は必要最低限の幅に落ち着いている。またタイトルについても何のウィンドウかが分かるようになった。ここは特に指示してないがLLMがいい感じにやってくれた。

最終編集時間から複数アカウントによる同時編集を判定し、同時編集の競合を阻止する機能を削除

v4.08で入れた別の仕組みで上書きを防止するようにしていたため、こちらの機能は削除した。

この機能は設定画面の整理をしているときに見つけた機能で、削除するまで存在すら知らず、私の場合はこの機能で上書き防止が機能したこともなかったので葬った。

サイトマッププラグインの設定を削除

そもそも何も設定できなかったため削除した。

やらなかったこと

本文見出しのレベルを一段階上げる

現状サイトタイトルがH1で、記事タイトルがH2である以上、必然的に本文の見出し#はH3始まりになってしまう。これをどうにか記事をH1にして、本文の見出しをH2で始めることができないかという内容だが、検討が終わらなかったため、実装を取りやめた。

2026-11-16追記

その後、H1タグの複数使用とページタイトルへの適用可否について軽く調べたで、一旦これをやっていく方針を出したので記しておく。

更新RSSを出せるようにする

PukiWikiに存在する機能だが、PukiWikiは更新RSSしか出しておらず、新規作成のRSSはないように見えたので、どっちか片方だけ出すなら更新用はいらないかというので実装を見送った。

というか更新情報欲しい人いるかどうか怪しいし、どっちかというと更新記事を読むのは検索流入のほうだろうからということにしておいた。

余談だがsitemap.xmlは更新日時を持っているため、こいつをパースすると更新した記事を取ること自体は可能である。

麻雀牌とVLゴシックフォント、360度画像の除去を検討

麻雀牌を使う人がいるかどうかは未知数だが、使ってみると思いのほかグラフィックの品質が良く、UTF8に入っているグリフを使うにも一般的な環境にはフォントが入っていないはずなので残すことにした。

VLゴシックフォントには非画像のメタデータをサムネイル表示するのに使われており、他に代わりの手段がないので残すことにした。

ここまで考えたところで360度画像は消す意味もないので残した。

バージョンの桁を文字列管理に変える

現状バージョンの下桁が2桁しかなく99に達したときに詰むので文字列化して3桁くらいにしようと考えたが、まだまだ余裕はあるし、そこに達する未来も見えないので一旦忘れることにした。

困ってから考えたらいい。前に検討したのよりマシな案とはいえ打つの面倒だしね。

コア機能を別リポジトリに切り出す

Satsuki systemは元々Perl製のWebフレームワークらしいし、adiaryもSakiaとして独立させようとしているように見えるので、私も本体から切り離して独立させようとしたが、まぁまぁ根が深かったし、分ける旨味もないのでやめることにした。

ぶっちゃけ現状のLyndbaumはadiaryに由来する超柔軟な構造を持つので、何か別のシステムを作りたければLyndbaumからフォークしたらいいだろうというので、コアの切り離しについてはなかったことにした。

adiary v3.52devからアップデートする場合

以下の手順でアップデートを繰り返すことで乗り換えられる。少なくとも、このブログはそうやって乗り換えてきている。とはいえ、既に十分魔境的な内容ではある。

  1. v4.29のタグをチェックアウトし、アップデート処理を行う
  2. v4.33のタグをチェックアウトし、アップデート処理を行う
  3. v4.34のタグをチェックアウトし、アップデート処理を行う
  4. v4.41のタグをチェックアウトし、アップデート処理を行う
  5. v4.43のタグをチェックアウトし、アップデート処理を行う

これだけ乗り換えが必要なのは全部後方互換性を切り捨てた破壊的変更の結果である。

あとがき

歴代のリリース記事を見ていたら機能追加・変更・削除・やらなかったの行数で、今回が最大の更新を叩き出すという恐ろしいバージョンになってしまった。しかもこれ、v4.34は0.8バージョン入って30行だが、v4.43は0.1バージョン分なのでえげつない。

Ver リリース記事の
変更関連見出し行数
v4.43 40
v4.34 30
v4.17 23
v4.19 19
v4.26 15
v4.22 14
v4.36 9
v4.20 6
v4.37 6
v4.42 5
v4.39 4
v4.40 3
v4.35 1

お陰で記事の頭でも書いた通り、またもや後方互換性を切り捨てたので本家からのアップデートは地獄である。記事をエクスポートしてインポートしたほうが早いかもしれない。ただまだその肝心な場所については検証していないので、実際にできるかどうかは不透明だが…w

今回のMilestoneで消化した41個のIssue

今回はタスクをGitHub Issueで作り、詳細の管理、やった理由、やらなかった理由などを書きつつMilestoneを使って消込管理をし、PRからそれらの情報をたどれるように工夫してみたが、やった内容が内容だけあり、カウントが面白いことになってしまった。

感触としてはまぁまぁ悪くはなかったと思う。以前PRからIssueをResolveするのは書くのが面倒なうえPR本文も意味不明気味になりがちだったが、こっちはPR本文に影響せず、情報の紐づけを出来るのが良かった。また対応漏れの管理もできる。

唯一の難点はGitHubはMilestoneのキャッシュにバグにしか見えない挙動があり、はがしたMilestoneがはがせてなかったり、いろいろちょっと面倒なことがあったことくらいだ。

激増したIssueのClose

今回の対応の関係でIssueのカウントも激増してしまった。何せ41件増えたからね。

残件の過半数はドキュメンテーションで機能開発は少ない

残件は13件で過半数はドキュメンテーションで、機能開発の比重は少ない。多分もうそんな思いつかないし気力もないと思うし、十分機能的にはよくなったので、後はこのままクロージングというか、今度こそは本当に穏やかになっていきたいところだ。

今回は変更量が多すぎて到底人力でのレビューができなかったため、Claude Fable 5.2 High, Claude Opus 5.5 Medium, GPT-6.1 Sol Mediumを総動員してクロスレビューを実施したが、なんとClaudeは一度トークンが切れてしまった。幸いFable 5.2リリース記念の無料リセットが使えたので事なきを得たが、ずいぶん盛大な戦いだった。GPTは以前サブスクを切ったため、このためだけに再契約して使った。

そして現在の残りがこんな感じ。Proなら95%の使用量でもう何もできないところだった。

そもそも土曜の14時前時点で26%とかいうPro換算130%とかいう激しい使い方になっていた。

Max x5は今回の契約限りで終わらせたいので、本当に落ち着かせていきたい…とはいえ、Lyndbaum以外にもやりたいことはあるので中々難しいところだが、ここまで盛大に使うことはないと祈りたいものだ…。

しかし2カラムテーマ対応で共通CSSを作ってしまったせいか、PageSpeed Insightsのパフォーマンスが2下がってしまったのが悲しい。ここは対応すべきかやや迷うポイントである…。恐らくHTTPリクエストが増えることによるオーバーヘッドのせいなので、共通化せずにベタのCSSを個別に作れば解決できる可能性はある。それをする旨味があるかどうかはさておきとして…。

2週連続で縦軸が埋まった

v4.40の時にGitHubの草が5年半ぶりに縦に並んだことを書いたが、なんと今回は二週連続でGitHubの草が縦に並び、二列も出来てしまった。もはや異次元である。

Lyndbaum v4.42アップデート

adiary置き換え計画始動から連なる、Lyndbaum v4.40アップデートの続き。

バージョンが0.2上がり、今回は主に個人的な困りごとを大きく改善した。ちゃんとやることが減っているためアップデート速度は落ちている。

文字数が過大になるとChromiumでMS-IMEがまともに動かなくなる問題の修正

Chromiumで<textarea>に8万字くらい入れた状態で、そのもっとも下の行で変換を行おうとした場合、MS-IMEがまともに動かなくなる問題があった。これを改善し、まともな速度になるようにした。

変更前 変更後

この問題はFirefoxでは起きないことを確認していたが、私はMS Edgeをブラウザにしているため対応した。対応としてはCodeMirror 6を入れることで対応した。40万字入れても安定しており、全文コピペも行える。CodeMirror 6は表示された領域以外をレンダリングから外しているらしく重くならないらしい。Opus 5.5からの受け売りなので本当かどうかは知らないが、軽くなったのは間違いがない。

これによって旅行記などの記事にある長大記事を一々VSCodeで書いて移植しなくてよくなったので、移植ミスが減ることが期待できる。

このこととは関係ないがChromium系はIMEに関する挙動が全般的におかしいと思っているのでどうにかなってほしい思いがある。例えばVSCodeで適当な文字列を選択した状態で何か入力すると既存の入力が崩壊する。これはGoogle IMEでも確か起きる。

エディタ上での構文強調に対応

上の問題解決にCodeMirror 6を使ったので、そのついでに対応した。CodeMirror 6はObsidianやTyporaなどに採用されているとされている、構文表示と構文強調を両立するためのライブラリなので、ついでに構文の強調もできるようにした。

変更前 変更後
変更前は素の表示 変更後は一部の構文が強調表示されるようになった

最低限の対応しかしてないのでそこまでリッチではないが、少し見栄えが良くなった。Lyndbaumで利用しているSatsuki記法もハイライトされるようにしてある。

WYISIWYGにすることも考えたのだが、これはやめることにした。理由は考えることがあまりにも多すぎるからだ。

例えば出来のいいNotionでさえバレットリストを数値リストに変換するには面倒なメニュー操作が必要だし、MilkDownでは表や画像を一括選択して消すなどの操作がそのままではやりづらく、選択範囲もわかりづらい。Altテキストやファイル名だけ差し替えるのも困難だ。

WYSIWYGを保ったままテキストのように扱うというのは、かなり困難なので今回は見送ることにした。複雑であるより単純であるほうが使い勝手はいいだろうという判断だ。ギークはNotionが好きだと思うが、一般人はまず使いこなせないのも、そういうことだと思っている。

検索ウィジェットに検索タイプを非表示にするオプションを追加

デザイナのウィジェット設定に検索タイプの非表示設定を追加した

デザイナで検索タイプの選択を非表示にできるようにした。

表示してる場合 非表示の場合
検索タイプのラジオがある ラジオがなく検索バーのみ

検索字はワードを叩いてエンターしたいので、ここでわざわざラジオを選択したくなかった。

選択は検索結果の画面で可能なので、そっちでするように倒したが、ここもLyndbaumテーマでは手入れして選択の手間を減らしている。

変更前 変更後
変更前はプルダウンメニューを開いて選択する必要があった 変更後はトグルボタンを選択するだけでひと手間減っている

変更前はプルダウンメニューを開いて選択する必要があったが、変更後はトグルボタンを選択するだけでワンアクション減っている。UX向上!

検索画面のファセット検索で親タグだけ出すようにした

変更前 変更後
変更前はすべてのタグが羅列されていて探すのが大変だった 変更後は親タグから選ぶのでマシになった

変更前はすべてのタグが羅列されていて探すのが大変だったが、変更後は親タグから選ぶのでマシになった。タグのインクリメンタルサーチもあると便利な気はするのだがUIをどうすればいいかが思いつかなかったので、一旦はこの形で止めている。

そもそもタグが無数にあるこのサイトが悪いと言われたら、それはそうな気はする。でも、adiaryの使い方としては理にかなっているというか、個人的にはてなダイアリーはPukiwikiの親戚だと思ってるので、あるべき姿なきはしている。

セキュリティ対応

  • 検索時のクエリパラメーターのc(ctype)の値をエスケープせずに画面に出していたのを直した
  • タグの無害化で、実体参照で書いたjavascript:が機能してしまう不具合の修正
  • メール送信のエラーを画面に出さず、エラーログに書くようにした
  • DBの行と設定のファイルを、書き終えてから置き換えるようにした
    • ファイルに出してる途中でOSが落ちたりするとファイルが壊れるため

あとがき

Lyndbaumの目標をどうするかを定めておらず、開発にブレができる懸念があったので一旦何をするためにやっているのかを考えることにした。

adiaryは「The "adiary" is high performance CMS」だったが、Lyndbaumは「The ultimate personal use lightweight self-hosted CMS for Japanese」にしようと思った。長いな。

端的に言うと日本語向けの軽量セルフホスト型CMSという感じ。Markdownと10万字オーダーの長文に対応し、ファセット検索や全文検索、バックリンクに対応し記事を探しやすく、高速に動作し、レンタルサーバーでも動く個人用のCMSみたいなところを目指したい。強欲だ。

旅行記や小説のような長大文章を書くに不自由せず、Wikiのような使い方もできる。将来的にはギャラリーサイトやWebサイトの作成(WordPressのように文字通りのCMS)ができるようにしたいところだが、風呂敷をどこまで広げるかはまだ考え切れていないが、まぁあくまで自分用で、使いたい人がいれば使ってね的なスタンスでぼちぼちやっていければと思っている。

そういえばバージョンの桁を増やしてMajor.Minor.Patch-Brach形式にしようと考えたのだが、バージョンを考えるコストが面倒なのと打つのが面倒なので、これは辞めることにした。桁が足りなくなったらx.yyのyyをyyyにすれば回避できるだろうということで、もし問題になったらその時に考えることにした。

Lokiのデータを永続化した

FluentBitでSystemdのログをLokiに送ってGrafanaで見れるようにし、ついでに設定をYAMLにしたでLokiのセットアップについて書いたが、Lokiのデータを/tmpに置いていたため、再起動などで揮発してログが見れなくなる問題があった。

そこで取り急ぎ永続化することにしたので、やったときのログを雑に残す。

確認環境

Env Ver
Ubuntu 26.04.1 LTS
Loki 3.5.9

前提

やったこと

  1. Lokiユーザーの作成と、永続化の保存先の作成
    sudo groupadd --system loki
    sudo usermod -g loki loki
    sudo chown -R loki:loki /var/lib/loki
    
  2. 現在のデータを永続化ストレージに移動
    sudo cp -a /tmp/loki/. /var/lib/loki/
    sudo chown -R loki:loki /var/lib/loki
    sudo du -sh /tmp/loki /var/lib/loki
    
  3. 保存先を永続化ストレージに変更
     common:
    -  path_prefix: /tmp/loki/
    +  path_prefix: /var/lib/loki/
    
  4. Lokiの再起動
    sudo systemctl restart loki
    

あとがき

以前バックリンク対応を入れたおかげで、こういう断片記事を書いても元記事からたどれるようになるので、今後は気軽にこういう断片をかけるようになると思うと便利だ。これまでは断片同士を探すのが地味に大変で、繋ぎ合わせるのに苦労していたが、それが少しは楽になると思っている。

しかし増えすぎるとノイズになるし、そのうちはやりWikiを作ったほうが良いようにも思える。幸いadiaryは元々Wikiとして使うことも想定されており、Lyndbaumでは差分や履歴管理にバックリンクまで入れているため、個人用Wikiとして使うには十分な機能を持ち合わせているので、実用性はあると思っている。