ABOUT THIS SITE
派手な演出を持ちながら速度を落とさないこと自体が、このサイトの主張です。 実装は変わり続けるため、現在の構成と変えた理由を、数値と一緒に公開します。
実測値
最終計測: 2026/10/23 8:28 / Lighthouse (mobile, simulated throttling, gzip配信)
- 99Performance目標 95 以上
- 100Accessibility目標 100
- 100Best Practices目標 100(警告)
- 100SEO目標 100(警告)
| 指標 | 実測 | 目標 |
|---|---|---|
| LCP | 1.74s | 2.0s 以下 |
| CLS | 0.000 | 0.05 以下 |
| TBT | 0ms | 200ms 以下 |
| 初期JS (gzip) | 5.4KB | 30KB 以下(設計) |
| フォント合計 | 165.5KB | 200KB 以下 |
数値は手打ちではありません。PRごとに Lighthouse で検証し、 main が更新されたときに測り直してsrc/data/metrics.json へ保存した値です。 予算を割った変更はマージできません。
このページだけ LCP の目標を 2.4s にしています。律速は HTML の量そのもので、文章を足すとそのぶん遅くなります。 内容を削るより数値を公開する方を選びました。他のページは 2.0s のままです。
技術スタック
何を使ったかは package.json を見れば分かります。 ここに書くのは、なぜそれを選んだかです。
フレームワーク
- Astro
- 静的HTMLを先に出し、必要な演出だけをクライアント側へ載せる。作品と意思決定ログは Markdown の Content Collections として管理し、ビルド時に検証している。
- TypeScript (strict)
- 演出の中身まで型を通す。React / Vue などのUIランタイムは加えず、Astro コンポーネントと素の TypeScript で実装している。
見た目
- 素のCSS + カスケードレイヤー
- Tailwind は使わない。デザイントークンを一次情報として持ちたかったのと、ユーティリティクラスの羅列ではこのページで説明しづらいため。
- Archivo / Zen Kaku Gothic New / IBM Plex Mono
- 全てセルフホスト。Google Fonts への実行時接続はしない(third-party接続はLCPを悪化させる)。
演出
- GSAP + ScrollTrigger
- 看板の点灯シーケンスと縦看板のパララックスに限定して使う。最初の操作か2.5秒経過まで取得を待ち、reduced-motion では取得しない。
- OGL
- 背景シェーダー1枚に必要な処理だけを扱える薄い WebGL ラッパー。GPUを使えない環境では初期化せず、CSSの背景を残す。
配信
- Cloudflare Workers (Static Assets)
- Worker スクリプトを持たないアセットのみの構成。公開URLは mawo.dev に一本化し、PR用のプレビューURLだけを残している。
- GitHub Actions + Lighthouse CI
- PRでは4ページを計測して予算を検証する。main 更新後はトップを再計測し、このページが読む実測値とフォントのサブセットを自動更新する。
こだわりと工夫
実際に踏んだ問題と、その対処です。このサイトで測って決めたことだけを載せています。
01
GPUが無い環境では背景シェーダーを動かさない
WebGLがCPU描画に落ちると、全画面のシェーダーを毎フレーム解くことになる。CIで Performance 0.71 / TBT 2180ms まで落ちた。描画元を見て初期化をやめ、CSSのグラデーションに任せている。
02
欧文は1ウェイトに固定する
可変フォント(20.1KB)もウェイト別2ファイルも、LCPを 1.86s → 2.10s に悪化させた。wdth 80 / wght 700 の単一インスタンス 9.3KB で固定している。
03
和文を入口用と残りに分ける
1ファイルにまとめると、トップがこのページの長文で使う字まで読み込む。unicode-range で core / ext に分割した。境界は実測で決めている。
04
演出は本文を配り終えてから取りに行く
GSAP と OGL は動的 import。さらに最初の操作か2.5秒経過まで、読み込みの判定自体を始めない。先に走らせたときは LCP が 1887〜2036ms の幅で振れた。
05
光の上でもコントラストを実測する
グローは背景を明るくするので、文字との比が下がる。ヘッダーのナビが青いネオンの上で 3.30:1 まで落ちていた(基準は 4.5:1)。文字を消した画面と比べて確かめている。
06
点灯の間隔は手で詰める
看板は 0.09 秒ずつずらして点灯させ、1枚あたり 0.14 秒で立ち上げてから 0.34 秒かけて微光へ落とす。速すぎると一斉に光って見え、遅いと焦れる。数式ではなく目で決めた。
07
計測は本番と同じ圧縮条件で行う
astro preview は無圧縮で配るため、そのまま測ると本番より悪い値が出る。gzip を返す静的サーバーを用意し、手元とCIで同じ条件にしている。
08
窓グリッドをやめて看板にした
当初は3:4の窓を敷き詰め、作品名はホバー時だけ出していた。タッチ端末には hover が無く、触るまで中身が分からない。1作品 = 1枚の看板に変え、名前と姿を常に見せている。
意思決定ログ
採用した技術より、採用しなかった選択肢のほうが設計を語ります。 各エントリには必ず「検討したが選ばなかったもの」を含めています。
なぜ Next.js ではなく Astro か
Next.js を使わない、と言うと「サーバーサイド処理をしないなら当然」と思われがちだが、実際に比較したのはビルド出力のデフォルト挙動だった。 Next.js は何もしなくてもクライアントランタイムが載る前提で設計されている。Astro は逆に、明示的に
client:ディレクティブを書かない限りJSが出力されない。このサイトは§8のパフォーマンス予算(初期JS 30KB以下)を「マージ不可」の交渉不可条件として掲げている。 デフォルトの挙動がその予算に沿っているフレームワークを選ぶことで、予算を守るための追加の努力(バンドル分割、動的import の徹底など)を最小化できた。
なぜ Lenis を入れなかったか
Lenis のようなスムーススクロールは、トラックパッドで触ると確かに気持ちがいい。マウスホイールのカクツキが消え、慣性が乗って「作られた質感」が出る。 ただしこれは体験の底上げではなく、入力デバイスによって体験差が生まれる実装でもある。
当時のこのサイトは、画面の暗さと光の集中でコントラストを作ることを中核の原則にしており、操作感を犠牲にしてまで得る演出的な気持ちよさとは方向性が違った。 削れる演出は削り、コアウェブバイタルを守るという判断はサイト全体で一貫させている。
2026 年 9 月 30 日に、暗さを優先するこの原則は廃止した。現在は夜の都市を背景として見せながら、作品と文章の読みやすさを優先している。Lenis を入れない判断は、INP と操作感の一貫性という理由のまま変わらない。
なぜ Three.js ではなく OGL か
WebGLの使用箇所は「光害の空」の1箇所のみで、しかもジオメトリはフルスクリーンを覆う三角形1枚だけ。カメラもライトもモデルローダーも要らない。
この要件に対して Three.js のシーングラフは機能過多であり、§8の初期JS予算(30KB以下、ただしWebGLは遅延ロードで初期には含まない)に対しても、遅延ロード時点での転送量は小さいほうが望ましい。 OGL はWebGL周りの定型処理だけを薄くラップしており、この用途に対する過不足のなさで選んだ。
なぜ Cloudflare Pages ではなく Workers か
当初の仕様では Pages を指定していた。理由は「静的配信で十分。エッジ配信が速い」で、これは事実として正しい。実際このサイトは SSR も API も持たないため、Pages で何も困らない。
見直したきっかけは「なぜ Pages なのか」という問い自体だった。調べ直したところ、判断材料が当初と変わっていた。
まず、Cloudflare は Pages を非推奨にしていない。ドキュメントに廃止の告知はなく、現役のプロダクトとして扱われている。一方で移行ガイドには「Unlike Pages, Workers has a distinctly broader set of features available to it」とあり、機能面では Workers がほぼ上位互換である。
そして Pages の実質的な優位だった Git 連携とプルリクエストごとのプレビューデプロイは、Workers Builds が同等に提供するようになった。ここで差がなくなった。
残る違いは
wrangler.jsoncを1つ置くかどうかだけになる。この状態で「今は要らないから狭いほうでいい」と判断する理由が見つからなかった。なお、この判断は速度のためではない。静的ファイルを配る限り両者の配信結果は同じで、Lighthouse の数値も変わらない。選んでいるのは性能ではなく、将来の選択肢の広さである。
このサイトは「機能を足さないこと」を繰り返し選んできたが、それは動くものを増やさないという意味であって、選べる余地を捨てるという意味ではない。前者はコストを減らすが、後者は将来のコストを増やす。
なぜグローを限定して使うか
制作開始時の 2026 年 1 月 18 日には、グローを原則禁止し、ホバー中の窓セル1つだけを例外にした。制約は守れたが、ネオン街を題材にしたサイトからネオンそのものが消えていた。
成果物一覧を「1作品 = 1枚の看板」に改めたことで、グローは飾りではなく情報を読ませる光になった。文字の芯を白に寄せ、色は内側・外側のグローと周囲への漏れ光に分ける。この規則は
neon.cssに集約している。解禁後も、光るのは成果物の看板、縦看板、選択中の導線などに限る。多色は使っても同じ強さで全面に広げず、文字が乗る場所では背景の最も明るい画素とのコントラストを測る。
2026 年 9 月 30 日に、暗さを優先する原則そのものを廃止した。現在は夜の都市を背景として見せながら、作品と文章の読みやすさを優先し、ネオンは世界観を補強するアクセントとして使う。グローを光源として意味のある要素に限る判断は、この方針のもとでも変わらない。
なぜ同名の GitHub リポジトリを改名したか
公開直後、「mawo.dev」で検索するとサイトより先に GitHub のリポジトリが出てきた。リポジトリ名をドメインと同じ
mawo.devにしていたためである。最初はこれを順位の問題として捉えていた。どうすればサイトを上に持ち上げられるか、という捉え方である。これが間違っていた。
検索エンジンの側に立つと、見えているのは同じ文字列を持つ2つのページでしかない。片方はソースコードの置き場で、もう片方は公開されたサイトである。両者が同じ人物のものだという情報がどこにもない以上、どちらを上に出すかは推測でしかない。順位を動かそうとする前に、識別の材料を渡していなかった。
打った手は2つだけである。リポジトリ名を
portfolio-siteに変えて文字列の衝突を消したこと。そして JSON-LD のPersonにsameAsを持たせ、GitHub と X のアカウントを列挙したこと。後者は「このドメインの持ち主は、そのアカウントの持ち主と同じである」という宣言にあたる。書いたのは名前・URL・アカウント・連絡先だけで、肩書きや実績のような主観は入れていない。構造化データは文脈も根拠も添えられない形式なので、そこに検証できない情報を置くと、主張ではなく飾りになる。言いたいことがあるなら本文で書けばよい。
技術的な下地はすでにあった。
sitemap-index.xmlもrobots.txtも canonical も最初から出している。何より、このサイトは JS を無効にしても全作品の本文とリンクが読める。クローラーにとってはこれが最も確実な条件で、しかも SEO のために用意したものではない。演出を遅延ロードへ追い出した設計が、そのまま効いているだけである。追記(2026年9月1日): 自動割り当ての
*.workers.devでも同じサイトが見えており、実際に別URLとして拾われたため、公開先をmawo.devのみにした。canonical を書くだけでなく、重複する入口そのものを閉じた。PRごとのプレビューURLは、マージ前の確認に必要なので残している。最後に、この判断には実測値がないことを書いておく。フォントの分割も preload の絞り込みも、すべて測った結果として決めてきた。しかし検索順位は自分で決められる指標ではなく、反映までの時間も制御できない。ここに書いたのは打った手であって、結果ではない。外れていたらこの項目に追記する。
