この記事で扱うこと
ファイルアップロード、CSSテーマ、パネルリサイズという一見異なる作業をしながら、3つとも**「単一の真実の源泉と、値が分かれる境界」**を明示しないと崩れるという共通点を整理した。
一日のまとめ
| 作業 | 何をしたかったか | やったこと | 結果 |
|---|---|---|---|
| アップロード | 4段階契約を欠けた段階なしに文書化 | presign → PUT → complete → create; ダミーURLなら PUT スキップ | complete 通知でバックエンドがアップロードを認識 |
| テーマ | @layer·@scope·light-dark() 軸分離 + 単一CSS生成器 | トークン抽象化、@layer で詳細度の戦いを排除、ランタイム・ビルドを一つの生成器に統一 | ランタイム・ビルドCSSがズレない |
| レイアウト | fr 比率保存 + localStorage はマウント後1回復元 | px → fr 比率保存; 復元を mount 後に延期 | ウィンドウサイズ自動対応 + hydration 一致 |
1. 直接アップロード契約
背景知識(概念)
- Presigned URL: サーバーが署名したURLでクライアントがストレージに直接 PUT する方式。
どんな状況だったか
大きなファイルをアプリサーバーで中継すると負荷・タイムアウトが大きい。クライアント → ストレージの直接アップロードが必要だった。4段階契約を欠けた段階なしに文書化するのが目標だった。
主な作業
大きなファイルをアプリサーバーで中継する代わりに、バックエンドが署名した presigned URL を受け取ってクライアントがストレージへ直接 PUT する。核心は段階契約: presign → PUT → complete → create。complete 通知を漏らすとバックエンドはアップロードを認識できない。モック環境で PUT をスキップするかは、グローバルフラグではなくURLがダミーかどうかで判断する。
教訓
- ストレージは PUT の成功をバックエンドに自動通知しない — complete 通知が必須だ。
- **「SoTがどこで、どこで値が分かれるか」**を明示すれば衝突を構造的に防げる。
2. CSSトークンテーマ
背景知識(概念)
@layer·@scope·light-dark(): それぞれ優先度・範囲・モードを担当するCSSの軸。互いに独立して扱わないと詳細度の衝突が起きる。
どんな状況だったか
ランタイム注入CSSとビルド成果CSSが分かれるとトークン・優先度がズレる。軸分離 + 単一CSS生成器が目標だった。
主な作業
テーマをトークン(カスタムプロパティ)に抽象化し、@layer(優先度)・@scope(範囲)・light-dark()(モード)をそれぞれ独立した軸として分離する。ランタイム注入CSSとビルド成果CSSが分かれないように、一つの生成器を単一の真実の源泉とする。結果として、ランタイム・ビルドCSSがズレない。
教訓
- テーマは優先度・範囲・モードを独立した軸に分け、CSS生成は一つの生成器に単一化する。
3. fr レイアウトとハイドレーション境界
背景知識(概念)
fr(CSS grid/flex): 余った空間を比率で分ける単位。px よりウィンドウサイズの変化に堅牢。- Hydration mismatch: サーバーHTMLとクライアントの初回レンダーが異なり、React が警告・ちらつきを出す現象。
どんな状況だったか
パネル幅を px で保存すると、ウィンドウサイズが変わったときの再計算が面倒で、localStorage をストア初期化時点で読むと SSR とクライアントの初回レンダーが異なる。fr 比率保存 + localStorage のマウント後1回復元が目標だった。
主な作業
パネルのドラッグリサイズで幅を px ではなく fr 比率で保存すると、コンテナ幅が変わっても再分配が自動になる。保存したレイアウトを localStorage から復元するときはマウント後1回に延期し、サーバーレンダーと初回クライアントレンダーを一致させる。ウィンドウサイズの自動対応 + hydration 一致を得た。
教訓
- 比率(fr)ベースの状態は絶対値(px)より環境変化に堅牢だ。
- localStorage・
windowはマウント後にのみ — SSR の初回レンダーと合わせる。