← ガイダンス一覧へ戻る

12画面の組み立てと配信

Zettelkasten Inbox / ブロック詳細

1〜11が「何をするか」なら、ここは「それがどこで動いて、どうやって画面が出るか」。サーバーが返すのは枠だけのHTMLで、中身は1件も入っていない。実行環境が要求ごとに変わるという性質が、他のブロックの弱点の根っこになっている。

抽象度 Lv.4(技術用語+システム上のやりとりまで/製品名・ライブラリ名は書かない)

1. 概要

ここまでの11ブロックが「何をするか」だとすれば、このブロックは「それがどこで動いて、どうやって画面が出てくるか」。 利用者がURLを開いてから、操作できる画面が立ち上がるまでの間に起きていることをまとめている。

いちばん大事な性質は1つ。 サーバーが返すのは「枠だけのHTML」で、中身のデータは1件も入っていない。 画面が立ち上がってから、ブラウザが改めて1〜11のAPIを呼んで中身を埋める。 だから受信箱を開いた直後、一瞬「受信箱を整えています」という読み込み中の表示が出る。

置かれている場所も特徴的で、利用者に近い場所に散らばって置かれた実行環境で動く。 この性質が、8番などで使っている「順番待ちの鍵」の効き方に直接効いてくる。

2. 位置づけ

利用者(URLを開く) → 12. 画面の組み立てと配信 → 1. アクセス確認 → 2〜11

3. 処理内容(Lv.4)

4. なぜこの位置にあるか(設計意図)

なぜ中身の入っていないHTMLを返すのか。 受信箱の中身を作るには、Notionの3つのデータベースを全件取る必要がある(2番)。 これをHTMLを組み立てるときにやると、画面が真っ白のまま数秒待たされる。 枠だけ先に返しておけば、「受信箱を整えています」という読み込み中の表示をすぐ出せて、 利用者は少なくとも「動いている」と分かる。

なぜ毎回作り直すのか。受信箱の中身は「いま何が残っているか」そのもの。 前に作ったHTMLを使い回すと、処理済みのノートがもう一度出てくる。 ただし実際には中身が入っていないので、使い回しても実害は無いはず―― それでも指定を付けているのは、あとで中身をHTMLに入れる作りに変えたときに事故らないようにする用心と読める。

なぜナビがページ全体の読み込み直しなのか。 ここには珍しく、コードの中に理由が書き残されている。 「配信環境で、それぞれの画面が確実に初期化されるようにするため」。 部分的な切り替えのほうが速いが、速さより確実さを取ったということ。 受信箱と全体マップは持っている状態がまったく違うので、 切り替え時に前の状態が残ると分かりにくい壊れ方をする。

接続情報を毎回写している理由。この実行環境では、設定値が プログラムの一般的な置き場所(環境変数)に自動では入らない。 だから要求のたびに、渡された設定を自分で環境変数へ写す。 これをやらないと、Notionを呼ぶ部分が「接続情報がありません」と 503 を返す。

5. 詳細プロセス分解

工程主体やりとりの内容方向
12-1ブラウザ → 配信の入口画面のURLへの要求
12-2配信の入口(内部)Notionの接続情報を環境変数へ写す
12-3配信の入口(内部)画像加工の専用パスかどうかの判定
12-4配信の入口 → 画面の組み立て3つの画面のどれかへの振り分け
12-5画面の組み立て(内部)宛先から題名・説明・OGPを組み立て
12-6画面の組み立て → ブラウザ枠だけのHTML(中身のデータは空)
12-7ブラウザ → 静的ファイル置き場文字・スタイル・画面の部品の要求
12-8静的ファイル置き場 → ブラウザ1年間保存してよい印つきの部品
12-9ブラウザ(内部)画面の起動と、1〜11のAPIの呼び出し開始

各行を文章にすると

12-1 利用者がURLを開くと、要求は利用者に近い場所に置かれた実行環境に届く。 1台の大きなサーバーではなく、世界中に散らばった小さな実行の場が、要求ごとに割り当てられる。

技術的には エッジ実行環境(利用者の近くで動く、 起動が速く軽い実行の仕組み)。1つの要求ごとに独立して立ち上がり、終われば片付けられる。 これが8番の「順番待ちの鍵」の効き方を決めている―― 鍵はその実行環境のメモリの中にしか無いので、 別の実行環境に割り当てられた同時操作には効かない。 同じ理由で、2番の30秒の控えや6番の6時間の控えも「運が良ければ効く」程度のものになる。

12-2 まず、この環境に設定されているNotionの接続情報を、 プログラムが読める場所へ写し替える。

技術的には 写すのは4つ:接続用のトークンと、 3つのデータベースの識別子。要求のたびに毎回実行される(1回だけではない)。 この実行環境では設定値が渡され方の都合で環境変数に自動で入らないため、手で橋渡ししている。 写す前に値があるかを確認するので、設定されていない項目は上書きされない。 この4行が無いと、Notionを呼ぶ処理はすべて 503 で止まる。

12-3 画像を小さく作り直すための専用の入口だけ、先に横取りする。

技術的には /_vinext/image というパスへの要求だけを 別扱いにし、決められた幅の一覧の中でのみ画像を変換して返す。 ただしこのアプリは、この仕組みを使っていない。 添付画像の表示も、原典プレビューの代表画像も、素の画像表示で書かれている (そのことを示す注記がコードに残っている)。 土台の雛形に最初から付いていた機能が、使われないまま残っている形。

12-4 それ以外の要求は、3つの画面のどれか、または12本のAPIのどれかへ振り分けられる。

技術的には 振り分けはフォルダの構成がそのままURLになる方式。 画面は3つ:/(受信箱)、/map(全体マップ)、/lab/recompose(再構成ラボ)。 3つとも「毎回サーバーで作り直す」指定が付いている。 なおAPIのほうは、ここを通ったあと 1番の門に入る。

12-5 画面の題名・説明文・共有したときに出るカードの絵を組み立てる。 このとき、いま自分がどのアドレスで呼ばれたかを見て決める。

技術的には 要求ヘッダの x-forwarded-host(間に立つ機器が「本来の宛先はここです」と伝えるための項目)と x-forwarded-proto を読んで、絶対URLの基準を組み立てる。 どちらも無ければ host を使い、それも無ければ手元の開発用アドレスにする。 つまり、置き場所を変えてもOGPのURLが自動で追従する。 題名は「Zettelkasten Inbox」、説明は 「Notionを正本に、Literature NoteをTagからABC Noteへ迷わず知識化する受信箱。」で固定。 全体マップの画面だけは別の題名を持つが、こちらは要求に関係なく固定値。

12-6 枠だけのHTMLが返る。受信箱の一覧も、Tagも、ABC Noteも、まだ1件も入っていない。

技術的には 返るのは、画面の外枠と読み込み中の表示だけを含むHTML。 中身を担当する部分は「ブラウザ側で動く」印が付いていて、 サーバーでは実行されない。だから HTMLの中を覗いても、Notionのデータは1文字も入っていない。 これは検索や共有に対しては不利(外から中身が見えない)だが、 このアプリは個人用で外に見せる必要が無いので問題にならない。

12-7 HTMLを受け取ったブラウザは、続けて文字(フォント)・見た目の指定・ 画面を動かす部品を取りに行く。

技術的には 文字は外部の配信元から読み込まない。 必要な字形だけを取り出したファイルが、このアプリと同じ置き場所から配られる。 外部の配信元に取りに行くと、電波の悪い所で文字だけ遅れて表示される(一瞬別の字体で見える)ので、 それを避けている。画面の部品は用途ごとに分かれていて、 受信箱の部品・全体マップの部品・再構成ラボの部品は別々。 受信箱しか開かない人は、全体マップの部品を受け取らずに済む。

12-8 これらの部品には「1年間そのまま保存してよい」という印が付いている。 2回目からは通信すら起きない。

技術的には Cache-Control: public, max-age=31536000, immutable。 31,536,000秒=365日。immutable「中身は絶対に変わらないので、 確認の問い合わせすら不要」という意味。 こんな強い指定ができるのは、ファイル名に中身から計算した文字列が入っているから―― 中身が変われば名前も変わるので、古いものを使い回す事故が起きない。 HTMLのほうには一切この印を付けない。 部品は永久保存、画面は毎回取り直し、という使い分けになっている。

12-9 部品が揃うと画面が動き出し、そこで初めて1〜11のAPIが呼ばれる。 ここから先が、これまでの11ブロックの話になる。

技術的には 受信箱なら 2番(一覧の取得)、 全体マップなら 10番、再構成ラボなら 11番が最初に呼ばれる。 画面上部のナビ(受信箱/全体マップ)は、部分的な切り替えではなく <a href> によるページ全体の読み込み直しで移動する。 つまり切り替えるたびに 12-1 からやり直す。 コードにはその理由が「配信環境で各画面が確実に初期化されるようにするため」と 書き残されている。速さより確実さを取った判断が、理由付きで残されている珍しい箇所。

ワークフロー図

← 図は横にスクロールできます

ブラウザ
配信の入口
画面の組み立て
静的ファイル置き場
12-1 画面のURLを開く
12-2 接続情報を環境変数へ写す
12-3 画像加工の専用パスかを見る
12-4 3つの画面のどれかへ振り分け
12-5 宛先から題名・説明・OGPを組む
12-6 枠だけのHTML(中身は空)
12-7 文字・スタイル・部品を取りに行く
12-8 1年間保存してよい印つきで返す
12-9 画面が動き出し、1〜11のAPIを呼ぶ

6. この分解から見えること

  • 「実行環境が要求ごとに変わる」という前提が、他のブロックの弱点の根っこ。 8番の順番待ちの鍵も、2番・6番・10番の控えも、全部その実行環境のメモリの中にある。 別々の実行環境に割り当てられた2つの操作の間では、鍵も控えも共有されない。 他のブロックで何度も出てきた「未確認」は、元をたどるとここに行き着く。
  • 正しさは鍵ではなく、書く直前の再取得と書いたあとの照合で担保している。 鍵が完全でないことを前提に、5番・8番・9番が毎回Notionから状態を取り直す作りになっている。 この設計は、12番の性質を分かったうえで書かれていると読める。
  • 中身の入っていないHTMLを返すのは、体感速度のための取引。 すぐ枠が出る代わりに、データが出るまでもう1往復かかる。 受信箱のように「3つのDBを全件取る」画面では、この判断は正しい。
  • 使われていない機能が土台ごと残っている。 画像の加工機能、データベース接続の一式、サインイン用の補助コード。 どれも雛形に最初から付いていて、消されないまま残っている。 動作には影響しないが、コードを読む人は「使っているのか」を毎回確かめることになる。
  • 部品の配り方は素直に良い。文字は外部を見に行かない、 部品は画面ごとに分かれている、名前に中身の値が入っているので1年間保存してよい。 ここは土台がよく出来ている部分で、アプリ側は何もしていない。

7. 失敗時の挙動

  • 接続情報が設定されていない:画面自体は普通に出る。 そのあとAPIを呼んだ時点で 503 +「Notion接続情報が設定されていません。」となり、 「Notionを読み込めませんでした」という画面に落ちる設定漏れは画面が出た後にしか分からない。
  • 画面の部品が取れない:枠だけが出て、そこから先へ進まない。 読み込み中の表示のまま止まる形になる。
  • 宛先ヘッダが無い:OGPのURLが手元の開発用アドレスになる。 画面の動作には影響しないが、共有したときのカードの絵が出なくなる。
  • 存在しないURLを開いた:振り分けに失敗し、配信の仕組みが用意する 既定の「見つかりません」の応答になる(アプリ独自の画面は用意されていない)。
  • どこまで進んでから失敗すると何が残るか: このブロックはNotionに一切触れないので、何も残らない。 利用者から見ると「画面は出たが中身が入らない」という形の失敗になる。

8. 未確認事項

  • 1つの実行環境がどれくらいの時間・何回の要求にわたって使い回されるか(控えと鍵の効き方が変わる/Lv.5相当の確認が必要)
  • 同時に複数の実行環境が立ち上がる条件
  • 画像の加工機能を今後使う予定があるか(いまは素の画像表示)
  • 存在しないURLを開いたときに何が表示されるか(アプリ側に専用の画面は無い)
  • 接続情報の設定漏れを、画面が出る前に気付ける仕組みを入れる予定があるか
  • 3つの画面に付いている「毎回作り直す」指定が、中身が空の現状で実際に効いているか

※ 2026年8月14〜15日時点の zettelkasten-inbox のプログラムを読んで作成しました。過去の設計資料は参照していません。

※ 番号は全体図・この解説・シーケンス図で共通です。工程番号は「ブロック番号-連番」で書いています。

全体図へ戻る / ブロック目次へ

akuramochi1.com — personal tools for learning and thinking.