技術の使い方を調べるとき、筆者は Web 検索より先に AI で当たりをつけています。手順や API の仕様なら、たいていそれで済んでしまいます。原典に当たりたいときでさえ、Web 検索から始めることはずいぶん減りました。
筆者が最後にこのサイトへ記事を書いたのは、2017 年 12 月 25 日でした。修士 2 年の冬から 9 年近く、ブログには何も書いていません。技術ネタは全部、職場の times に流していました。
そもそも AI が手順を教えてくれて、情報発信にしても times なら数分で同僚からの反応が返る時代です。記事を配信するためだけに、わざわざ個人サイトを作り直す理由はありません。それでも作り直したのは、答えだけでなく、そこへ至るまでの思考も記録して、伝えてみたくなったからです。ついでに、読み終わった人が気軽に反応を返せるようにしてみました。
この記事では、times から離れて個人サイトへ戻った理由と、読者に記事を最後まで読んでもらうために、サイトにどんな機能を持たせたかをまとめました。
times は居心地がよすぎた
筆者の職場には times 文化があります。各自が自分のチャンネルを持ち、作業ログでも技術メモでも独り言でも好きに流します。筆者もそこに、調べたことや詰まったことを書いていました。
書いてから「書いてよかった」と思えるまでが、とにかく短いんですよね。誰かが読んで、質問や絵文字のリアクションを返します。「それ自分もハマりました」の一言が返ってくることもあります。書いてよかった、とすぐ思えます。
ただし、times に書いたものは社外に出ません。読む人は同僚に限られます。この 9 年近く、対外的な発信はほとんどありませんでした。届く範囲が社内に閉じると分かっていても、times は居心地がよすぎました。
読みたくなる記事には思考が書かれている
筆者自身が AI で先に調べるようになっても、個人のブログには最後まで読んでしまう記事があります。調べたかったことは最初の数行で済んだのに、そのまま惹き込まれて気がついたら読み終えてしまいます。
そういう記事には、答えだけでなく、思考の過程が書かれています。なぜその設計にしたのか、何を試して駄目だったのか、どこで詰まったのか。「A を採用した」という結論だけでは、ほかの案を退けた理由も、何を守るための選択だったのかも分かりません。答えは残っても、条件が変わったときに考え直す材料は残りません。
AI が手順を教える時代なら、人間が書く記事にはナラティブを意識した思考の過程を残したい。それを読んでもらう場所として、個人サイトをもう一度選びました。
情報発信だけのサイトをやめる
旧サイトは Jekyll と GitHub Pages でした。 Markdown を書いて push すると静的な HTML が生成されて配信される、よくある構成です。 2010 年から 2017 年までの記事が 27 本あって、高校生のときに書いたものも混ざっています。読み返すとなかなか恥ずかしいのですが、消さずに全部移しました。昔の URL も 308 リダイレクトで生かしています。
静的サイトのままでも記事は配信できます。でも今回ほしかったのは、ファイルの置き場だけではありません。 times のように、いろいろな emoji でリアクションできる機能がほしい。どうすれば人間に最後まで読んでもらえるのか、サイトを使って試してみたい気持ちもありました。
そこで、サイトを Cloudflare Workers の上に作り直しました。記事のメタデータは D1、本文と画像は R2、読者ごとの状態は KV に置いています。モダンな技術スタックで、いわば "対外的 times" を作ったわけです。
実装そのものは公開しているので、細かいところはリポジトリを読むか、AI に聞いたほうが早いはずです。よかったら覗いてみてください。
GitHub - yantene/yantene.netContribute to yantene/yantene.net development by creating an account on GitHub.モダン Web の当たり前をそろえる
まず、モダン Web として求められる機能には一通り対応しました。
- PWA: ホーム画面にインストールでき、一度読んだページは Service Worker によってオフラインでも開けます
- レスポンシブ対応: スマートフォンから大きな画面まで、同じ記事を無理なく読めます
- 画像: 遅延読み込みに対応し、同期時に取得した寸法を HTML に埋めてレイアウトシフトを防ぎます
- アクセシビリティ: 操作部品に ARIA 属性を付け、端末の設定に応じてアニメーションを抑えます
- メタデータ: OGP、構造化データ、Atom フィード、sitemap を出します
- セキュリティ: スクリプトは nonce を使った Content Security Policy で縛ります
ここまでは、記事の中身以前の話です。
記事の原文を、そのまま渡す
読んだものを読者がどう扱うかは、読者が決めることです。手元に保存する、引用する、AI に要約させる。どれも書き手が口を出す話ではありません。
記事の URL の末尾に .md を付けると、原文の Markdown がそのまま返ります。 .md を付けなくても、Accept: text/markdown で名指しすれば同じものが返ります。もらったリンクをそのまま AI に渡すときに、URL をいじらずに済みます。
名指ししたときだけ、というのが要点です。ブラウザの Accept には必ず */* が入っているので、これを Markdown と数えると、記事を開いた全員に原文が配られます。ワイルドカードは HTML に倒し、Markdown は名指しでしか取れないようにしました。
返すのは、GitHub に置いた Markdown そのものです。フロントマターも含みますし、画像の相対パスも書き換えません。加工した Markdown を原文と呼びたくなかったからです。
「よく読まれている」を、どう決めるか
トップページには、よく読まれている記事のランキングを出しています。ナイーブに実装するなら、アクセス数の多い順に並べるでしょう。ただ、それでは新着記事に不利で、古い記事がいつまでも上位に居座ります。そこでアクセスカウンタの重みに半減期を設け、最近のアクセスほど優遇することにしました。
ところが、重みを時間とともに減らすには、全記事のスコアを定期的に計算し直すバッチが要ります。そこで発想を裏返しました。古い重みを減らすのではなく、新しいアクセスの重みを増やします。基準日から半減期 1 つぶん進むごとに、そのとき起きたアクセスの重みを 2 倍にします。こうすると、一度記録した値には二度と触らなくて済みます。バッチが丸ごと消えました!
ここからは記事の本筋を少し外れるので、急ぎの人は次の節まで飛ばし読みして OK です。とはいえ、趣味のサイト作りで一番楽しいのは、こういう寄り道ですよねw
重みは経過に対して指数的に膨らみます。基準日を 、半減期を 日とすると、 日目に起きたアクセスの重みはこうなります。
半減期を 30 日にすると、素の値はやがて倍精度に収まらなくなります。基準日を 2000 年 1 月 1 日に置いているので、ざっと計算すると限界は 2080 年代のどこかでしょうか。そのころ 90 歳近いやんてね翁は、きっとこの制約を忘れて記事を量産していることでしょう。そんな中、全記事のスコアが になってランキングが壊れたら大変です。
そこで、スコアは対数で持つことにしました。
指数が消えて、経過に対して線形にしか増えません。 100 万年でも にしかならない計算です。
しかも、対数で持つと嬉しい副産物があります。対数は単調なので、順序は素の値のときと変わりません。保存した列をそのまま降順に並べれば人気順になります。読み出すときに計算を挟まずに済みます。
足し算にはいわゆる log-sum-exp を使います。素の値に戻すと溢れるので、対数のまま和を求めるやつです。 と の大きいほうを 、小さいほうを とすると、和の対数はこう書けます。
大きいほうを括り出してから足すので、指数関数の引数が 0 以下に収まり、途中の計算でも溢れません。リアクションもこの足し算に乗せていて、1 つを閲覧 5 回ぶんとして数えています。
……と、ここまでは順調でしたが、最後にひとつ詰まりました。まだ読まれていない記事のスコアを 0 にすると、対数が になります。下限を「投稿日に 1 回読まれた」ぶんに引き上げて解決しました。おかげで初回かどうかで場合分けする必要がなくなり、まだ読まれていない記事同士も新しい順に並ぶようになりました。
この仕組みだと、古い記事が上位に来ることがあります。それは昔の記事がいまも読まれているということなので、正しい順位です。
リアクションは作り、コメント欄は作らない
冒頭に戻ります。 times で得ていたのは、速さと絵文字のリアクションでした。
リアクションは作りました。記事の上と末尾に置いたハートを押すと「いいね」になり、パレットから別の絵文字も選べます。押せるのは 1 記事につき 1 つです。
D1 が持つのは、どの絵文字が何回押されたかだけです。誰が押したかは残しません。その代わり、読者が何を押したかは、読者ごとのセッションに置いています。だから押し直しも取り消しもできますし、誰が何を押したかの一覧はどこにも残りません。
一方でコメント欄は作りません。感想も議論も、Bluesky や X に逃がします。
自前でコメント欄を持てば、悪口を書かれても非表示にできます。それでも、会話の場は読者が普段いる場所に置きます。
読者は筆者にコメントしたいというより、自分の意見を書いて他人とやりとりしたいことが多いはずです。会話の場は読者のホームであるべきで、筆者のサイトに呼びつけるのは筋が違います。閉じこもるより記事の宣伝にもなります。
リアクションを自分で持ちながらコメントを外に出すのは、ちょっとちぐはぐに見えますよね。リアクションは、記事が読まれた手応えを筆者が受け取るためのものです。コメントは、書き手も加わる会話です。前者だけ自分で持ちます。
会話を外に置いても、外で交わされた会話との接点までなくしたわけではありません。このサイトは Webmention に対応しています。他所のサイトからリンクされたことを、リンク元がこちらに通知する仕組みです。会話は外、参照は内。
速さのほうは諦めました。
times に書けば、数分で反応が返ります。ただし、読むのは同僚だけです。ここに書けば、反応は何日か経ってから来ます。届かないまま終わるかもしれません。
その代わり、読む人を同僚に限定しません。学生のころの記事も、筆者が放置していた 9 年近くのあいだ、そこに残っていました。
あとは面白い記事を書くだけ
今回、思い切って個人サイトを作り直しました。読むのを邪魔するものを減らし、原文をそのまま渡し、読み終わった人が反応を返せる場所にしました。まだ試したいアイデアはたくさんあります。
ただし、人に読まれる仕組みだけでは、人に読まれる記事になりません。面白い題材を見つけ、それを面白く伝える文章力が要ります。ここだけは Cloudflare Workers に載せても動きません。困ったもんですねw
ともあれ、サイトも文章もチューニングしながら、また書いていきます。これからよろしくお願いします!
