一覧へ

AIと3日でブログを作り終えた

2023年にリポジトリだけ作って放置。2026年の夏、3日69コミットで復活させた記録。

· 3分で読める

git logを開けば、このブログの歴史が一目でわかる。 2023年1月10日、やる気満々でリポジトリを作り、セットアップのコミットを積んだ。 6月までにぽつぽつと40あまりのコミットを残して、そのまま姿を消した。 その40コミットも、中身はほとんどセットアップだ。 リントルールを選び、フォルダ構成を整え、コンポーネント名に悩んだ。 肝心の記事は一本も書かなかった。

3年間、何も起こらなかった。

ブログは明日から

そして2026年7月20日にまた開いて、22日に終わった。 3日間で69コミット、うち46コミットは2日目だけで積まれた。 3年先延ばしにした割にはあっけなく終わって、いったい何が怖くて開かなかったのかと思った。

3日間に起こったこと

コミットログを日付ごとに見ると、3日それぞれ性格が違う。

1日目は工事現場だった。 パッケージマネージャをbunに替え、デザイントークンとダークテーマを整え、i18nextを引き剥がして自作i18nにし、日本語まで足した。 記事の一覧・詳細・タグページ、Supabaseベースのサーバーレスコメント、OG画像・サイトマップ・RSSといったSEOの土台、404ページ。 夕方には骨組みが立っていた。

2日目は46コミットの暴走だった。 ⌘K検索、ダークモードのリデザイン、文単位のハイライト共有、閲覧設定、モバイルのスワイプ、コメントのランダムニックネーム、コナミコマンド。 この記事で自慢するものは、ほぼこの日に生まれた。 合間にNext.js 16へのアップグレードやBiomeへの移行みたいな工事まで挟まっていて、昔ならこれだけで週末が一つ潰れていたはずだ。

3日目は締め切りだった。 ファビコンを「二つの目」のデザインに替え、Search ConsoleとNaverに登録し、OGカードを作り直した。 そしてこの記事を書いた。

もちろん、このペースは一人で出したものではない。

なぜ3日で足りたのか

変わったのは私ではなく道具だ。 今回は最初から最後までAIと一緒に作った。

去年 MVPはまだ有効か という記事で「AIのおかげで『最小』の基準が上がった」と書いたが、その記事の証拠品がこのブログになるとは思わなかった。 ページ遷移、スワイプジェスチャー、文単位の共有、コメントシステム。 昔なら一つひとつが「いつか付けたいもの」リスト行きだった機能が、3日で作った個人ブログに全部入っている。 個人ブログの「最小」も上がったのだ。

作り方も変わった。 昔の私ならトーストライブラリ選びに半日使っていたはずだが、今回は選ぶ代わりに作った。

ライブラリのドキュメントを精読する vs AIとそのまま自作する

見栄ではなく経済的な判断だった。 AIと組めば、大抵のインタラクションは200行以内で出てくる。 ライブラリのドキュメントを読んで設定と格闘する時間と、欲しい動作をゼロから作る時間が、ほぼ同じになった。 それなら自分の手に馴染むほうを選ぶことになる。 このブログのトーストも、ジェスチャーも、コメントも、全部そうやって自作したコードだ。 おまけに、全部自分で動作を説明できるコードでもある。

このページでそのまま試せるもの

3日間で作ったものから三つだけ選んだ。 三つとも、いま読んでいるこのページで動いている。 モバイルなら画面を横に引っ張って、デスクトップなら⌘Kを押してみてほしい。

1. モバイルで記事をめくる

モバイルで記事を読み終えると、一覧に戻って次の記事を探すことになる。 その動線が嫌で、ページをめくるように横に引っ張って移動できるようにした。

記事を横に引っ張ると前の記事のタイトルが現れ、離すとスライドで移動する

ジェスチャーライブラリなしでタッチイベント三つで作ったが、コードより判定ルールを決めるほうが仕事だった。

  • 横の移動が10pxを超え、縦の1.2倍より大きいときだけスワイプと見なす
  • 判定前に縦へ12px以上動いたらスクロールの意図と見なし、最後まで無視する
  • コードブロックや表みたいに横スクロールを持つ要素で始まったタッチも無視する
  • 一度スワイプと認定されたら、そこから preventDefault() でスクロールをロックする

引っ張る「感触」は関数一つが作っている。

ts
const THRESHOLD = 88;
const damp = (p: number) =>
  0.32 * Math.min(p, THRESHOLD) + 0.14 * Math.max(0, p - THRESHOLD);

指が動いた分だけ画面を動かすと安っぽく見える。 しきい値までは0.32倍、その先は0.14倍に圧縮して初めて、ゴムのような抵抗感が生まれる。 この係数はAIには決められない。 実機で指で引っ張りながら合わせた値だ。 離すとビュートランジションのスライドが引き継いで、引っ張っていた方向にページが流れていく。 移動できる記事は先にprefetchしてあるので、離した瞬間の待ちもない。

録画中に笑える出来事もあった。 確かに作った機能なのに、左に引っ張っても何も起こらず、バグかと思った。 実は最新の記事だから次の記事がなく、行き先がなければジェスチャー自体を拒否するように私が書いておいたのだった。

私が書いたガードロジック vs スワイプできないと怒る私

2. トーストの代わりにカプセル

このサイトに通知トーストはない。 リンクのコピーもテーマの切り替えも、通知はすべて上部のナビゲーションカプセルが伸びて見せる。 記事を開くと「読むのに約4分かかります」と先に教えてくれるのも、このカプセルだ。

テーマを切り替えるとカプセルが伸びてメッセージを見せる

CSSは width: auto へのアニメーションができない。 だから現在の幅をピクセルで固定し、次のフレームで目標の幅を指定するFLIP方式で作った。 状態管理は70行の自作ストア一つ。 どのコンポーネントからでも islandStore.notify() 一行で、カプセルが代わりに喋ってくれる。

機能が動いてからは、むしろ細かいルールに時間を使った。 スクロールを下げると隠れ、上げると現れる。ただし6px以下の動きは無視して震えないようにした。 喋っている最中、つまりメッセージが出ている間は、スクロールしても隠れない。

半日を溶かしたバグもここで出た。 ある日カプセルのブラーが丸ごと消えたのに、カプセルのコードはどう見ても正常だった。 犯人は、レイアウト整理中に親ラッパーへ入れた overflow の一行。 祖先要素にクリッピングが生まれると backdrop-filter が静かに死ぬと、この日知った。 バグはいつも、自分が見ていないファイルにいる。

3. ドメインの秘密

⌘Kを押すと検索パレットが開くが、実はこれはターミナルでもある。 helptheme darkwhoami といったコマンドが通り、要は qwerty だ。

⌘Kパレットに qwerty 최민기 と打つと chlalsrl が出力される

qwerty 최민기 と打つと chlalsrl が出る。 韓国語キーボードはQWERTY配列を共有しているので、入力モードを切り替えずに私の名前「최민기(チェ・ミンギ)」を打つと、そのまま chlalsrl になるのだ。 このドメインがなぜ chlalsrl.com なのかという答えを、パレットの中に隠しておいた。 ドメインを口で伝えるたびに説明が長くなるのが欠点だが、説明を聞き終えた人は一発で覚えてくれる。

矢印キーでコナミコマンドを打つとカプセルが出迎えてくれるし、コンソールにはデプロイされたコミットハッシュが刻まれている。 誰にも見つからなくても構わないが、いつか誰かが⌘Kに sudo と打って笑ってくれたら、作った甲斐は十分ある。

もう一つ: 文の共有

三つと言ったが、これは素通りできない。 本文の文章を選択すると共有ボタンが出て、押すとその文へ直接飛ぶリンクがコピーされる。

本文の文章を選択すると共有ボタンが出て、押すと文のリンクがコピーされる

記事のリンクを丸ごと共有されるより、誰かが私の記事の一文を抜いて送ってくれるほうがずっと嬉しい。 その一文が、その人に残ったという意味だから。

面白いのは、この機能でサーバーが何もしていない点だ。 リンクはブラウザ標準のText Fragment(#:~:text=)記法なので、開いた側のブラウザが勝手にその文までスクロールし、ハイライトまでしてくれる。 私のコードは選択範囲を検知してURLを組み立てるだけ。 共有された文は集計しておいて、複数人が重なると記事の下に「読者が線を引いた一文」として表示する。 どの文が残るのか、私も気になっている。

AIと働いてみて

3日のうち、コードを書いた時間はAIのほうが圧倒的に長い。 では私は何もしていなかったかというと、まったく違う。 役割が違っただけだ。

AIは作るのがうまい。 スワイプのタッチイベント処理、カプセルのFLIP、Supabaseの接続。説明すればすぐ出てくる。 ダークモードでリロードしたときの白い点滅を防ぐために、最初の描画前にインラインスクリプトを仕込む、みたいな知る人ぞ知るコツも勝手に押さえてくる。

代わりに「どのくらい」は決められない。 減衰係数の0.32、カプセルが隠れるスクロール基準の6px、ニックネームが替わる間隔の70ms。 こうした数字は全部、自分で触って決めた値だ。 画面を触らずに良い値を出す方法はないし、指先の判断までは任せられなかった。

微妙なバグ取りも、結局は一緒にやった。 韓国語のタグページが全部404になったことがあって、ビルド時にタグを先にエンコードして渡していたのが原因だった。 フレームワークがもう一度エンコードして、二重エンコードされたパスでページが作られていたのだ。 症状から原因が見えにくい類いのバグで、こういうのは今でも人間が状況を絞り込まないと捕まらない。

作って、消したもの

今回の作業でいちばん意外だったのは、消す決断をこんなにたくさんすることになるとは思わなかった、という点だ。 レシートは全部コミットログに残っている。

  • ゲストブックのページを作って、数時間後に消した
  • いいねを複数絵文字のリアクションバーに替えて、次のコミットで元に戻した
  • ホームにプロジェクト欄と更新履歴を付けて、その夜に外した
  • スワイプのプレビューは矢印チップにラベルカードまで賑やかだったが、二度作り直してオーバーレイとタイトルだけ残した
  • コメント入力カードにはスタイルのコミットだけで四つ積まれた
  • OGカードは6番目のバージョンまで行って、結局3番目に戻した

ハイライトはOGカードだ。 共有時に見えるカードに「二つの目」のマスコットを入れて、コミットログ基準で3分後に外した。 00:51に追加、00:54に削除。 1200×630の中では、タイトルが主役でなければならないのに、目が視線を奪い続けたからだ。

自動生成されるOG画像

作ることが安くなったから、まず作ってから判断できるようになって、何を残して何を捨てるかを選ぶことが本業になった。 コードはAIが書き、私は消すものを決めた。 どちらがプロダクトを作る仕事かと聞かれたら、消すほうだと答える。

そのほかに入ったもの

ここまで書いても、まだ残っているものがある。

コメントフォームは、名前欄が空のまま開くことがない。 最初からランダムなニックネームが入っていて、隣のボタンを押すとスロットのように新しい名前が回る。 アバターは名前のハッシュで描くので、同じ名前ならいつも同じ顔になる。 名前欄が空だと何を書くか迷ってタブを閉じてしまう、というのが私の仮説で、当たっているかはコメントが積もってみないとわからない。

名前ガチャのボタンを押すとニックネームがスロットのように回って止まる

読む側にも手を入れた。 集中モードをオンにすると画面中央付近の段落だけが明るく残る。長文で目がよその行に泳ぐ私が最初のユーザーだ。 ダークモードで文字をドラッグするとデフォルトの反転選択が白く光っていたので、アクセントカラーを少しだけ混ぜた面に替えて抑えた。

ファビコンの二つの目は、画像ファイルなしでコードで描いている。 原案SVGの比率を定数に写してあるので、64pxのファビコンから512pxのホーム画面アイコンまで同じコードから生まれる。 言語は韓国語、英語、日本語の三つに対応していて、翻訳がない記事は原文にフォールバックする。 この記事は三つとも揃っている。

3日あれば足りる

3年先延ばしにした理由をいま考えてみると、頭の中の見積もりが2023年で止まっていた。 ブログの作り直しを数か月がかりの仕事として書き留めていたから腰が上がらず、腰が上がらないから開きもしなかった。 道具は変わったのに、見積もりだけ昔のものを抱えていたわけだ。 開いてみたら、3日だった。

作るものは全部作ったので、あとは書くだけだ。

関連記事

コメント

    一覧へ