ABOUT THIS SITE

派手な演出を持ちながら速度を落とさないこと自体が、このサイトの主張です。 実装は変わり続けるため、現在の構成と変えた理由を、数値と一緒に公開します。

実測値

最終計測: 2026/10/23 8:28 / Lighthouse (mobile, simulated throttling, gzip配信)

  • 99Performance目標 95 以上
  • 100Accessibility目標 100
  • 100Best Practices目標 100(警告)
  • 100SEO目標 100(警告)
パフォーマンス予算の実測
指標実測目標
LCP1.74s2.0s 以下
CLS0.0000.05 以下
TBT0ms200ms 以下
初期JS (gzip)5.4KB30KB 以下(設計)
フォント合計165.5KB200KB 以下

数値は手打ちではありません。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 更新後はトップを再計測し、このページが読む実測値とフォントのサブセットを自動更新する。

こだわりと工夫

実際に踏んだ問題と、その対処です。このサイトで測って決めたことだけを載せています。

  1. 01

    GPUが無い環境では背景シェーダーを動かさない

    WebGLがCPU描画に落ちると、全画面のシェーダーを毎フレーム解くことになる。CIで Performance 0.71 / TBT 2180ms まで落ちた。描画元を見て初期化をやめ、CSSのグラデーションに任せている。

  2. 02

    欧文は1ウェイトに固定する

    可変フォント(20.1KB)もウェイト別2ファイルも、LCPを 1.86s → 2.10s に悪化させた。wdth 80 / wght 700 の単一インスタンス 9.3KB で固定している。

  3. 03

    和文を入口用と残りに分ける

    1ファイルにまとめると、トップがこのページの長文で使う字まで読み込む。unicode-range で core / ext に分割した。境界は実測で決めている。

  4. 04

    演出は本文を配り終えてから取りに行く

    GSAP と OGL は動的 import。さらに最初の操作か2.5秒経過まで、読み込みの判定自体を始めない。先に走らせたときは LCP が 1887〜2036ms の幅で振れた。

  5. 05

    光の上でもコントラストを実測する

    グローは背景を明るくするので、文字との比が下がる。ヘッダーのナビが青いネオンの上で 3.30:1 まで落ちていた(基準は 4.5:1)。文字を消した画面と比べて確かめている。

  6. 06

    点灯の間隔は手で詰める

    看板は 0.09 秒ずつずらして点灯させ、1枚あたり 0.14 秒で立ち上げてから 0.34 秒かけて微光へ落とす。速すぎると一斉に光って見え、遅いと焦れる。数式ではなく目で決めた。

  7. 07

    計測は本番と同じ圧縮条件で行う

    astro preview は無圧縮で配るため、そのまま測ると本番より悪い値が出る。gzip を返す静的サーバーを用意し、手元とCIで同じ条件にしている。

  8. 08

    窓グリッドをやめて看板にした

    当初は3:4の窓を敷き詰め、作品名はホバー時だけ出していた。タッチ端末には hover が無く、触るまで中身が分からない。1作品 = 1枚の看板に変え、名前と姿を常に見せている。

意思決定ログ

採用した技術より、採用しなかった選択肢のほうが設計を語ります。 各エントリには必ず「検討したが選ばなかったもの」を含めています。

  1. なぜ Next.js ではなく Astro か

    判断したこと
    フレームワークに Astro を採用し、Next.js は採用しない。
    検討した選択肢
    • Next.js(App Router)サーバー機能(SSR/API Routes/ミドルウェア)が本サイトには不要。コンテンツは本質的に静的で、動的な処理はホバー演出や点灯シーケンスなどクライアント側の島だけで完結する。フルスタックフレームワークを選ぶ理由がない。
    • SvelteKit / Nuxt などの他フルスタックフレームワークNext.js と同じ理由に加え、Astro のアイランドアーキテクチャほど「JSをデフォルトゼロにする」思想が徹底されていない。
    決め手
    静的HTMLをビルド時に出力し、JSは必要な箇所だけ島として載せられること。演出過多なサイトほどベースの出力JSは軽くあるべきだと考えた。

    Next.js を使わない、と言うと「サーバーサイド処理をしないなら当然」と思われがちだが、実際に比較したのはビルド出力のデフォルト挙動だった。 Next.js は何もしなくてもクライアントランタイムが載る前提で設計されている。Astro は逆に、明示的に client: ディレクティブを書かない限りJSが出力されない。

    このサイトは§8のパフォーマンス予算(初期JS 30KB以下)を「マージ不可」の交渉不可条件として掲げている。 デフォルトの挙動がその予算に沿っているフレームワークを選ぶことで、予算を守るための追加の努力(バンドル分割、動的import の徹底など)を最小化できた。

  2. なぜ Lenis を入れなかったか

    判断したこと
    慣性スクロールライブラリ(Lenis)を導入しない。ネイティブスクロールのまま実装する。
    検討した選択肢
    • Lenis を導入し、全ページに慣性スクロールを適用するネイティブのスクロールイベントを乗っ取る実装のため、トラックパッド以外(マウスホイール、キーボード、スクリーンリーダーの読み上げ操作)での挙動が不自然になりやすい。INP(Interaction to Next Paint)にも悪影響が出る。
    • 特定セクションだけ Lenis を部分適用するスクロール挙動がページ内で切り替わること自体が違和感の原因になる。統一感を優先した。
    決め手
    「気持ちよさ」の対価としてコアウェブバイタルと操作感の一貫性を払う価値がないと判断した。成果物の看板が点灯する演出(GSAP ScrollTrigger)はネイティブスクロールの上でも十分に成立する。

    Lenis のようなスムーススクロールは、トラックパッドで触ると確かに気持ちがいい。マウスホイールのカクツキが消え、慣性が乗って「作られた質感」が出る。 ただしこれは体験の底上げではなく、入力デバイスによって体験差が生まれる実装でもある。

    当時のこのサイトは、画面の暗さと光の集中でコントラストを作ることを中核の原則にしており、操作感を犠牲にしてまで得る演出的な気持ちよさとは方向性が違った。 削れる演出は削り、コアウェブバイタルを守るという判断はサイト全体で一貫させている。

    2026 年 9 月 30 日に、暗さを優先するこの原則は廃止した。現在は夜の都市を背景として見せながら、作品と文章の読みやすさを優先している。Lenis を入れない判断は、INP と操作感の一貫性という理由のまま変わらない。

  3. なぜ Three.js ではなく OGL か

    判断したこと
    背景シェーダー(光害の空)の実装に OGL を採用し、Three.js は採用しない。
    検討した選択肢
    • Three.jsフルスクリーン三角形1枚を描くだけの用途には機能範囲が広すぎる。シーングラフやローダーなど、使わない機能まで含む構成を選ぶ理由がない。
    • 生の WebGL API を直書きシェーダーのコンパイルやuniform管理などの定型処理まで自前で書くことになり、可読性と保守性が落ちる。得られる軽量化に対して割に合わない。
    決め手
    本サイトはWebGLをフルスクリーン三角形1枚のフラグメントシェーダーにしか使わない。必要な定型処理だけを扱える薄いラッパーで十分だった。

    WebGLの使用箇所は「光害の空」の1箇所のみで、しかもジオメトリはフルスクリーンを覆う三角形1枚だけ。カメラもライトもモデルローダーも要らない。

    この要件に対して Three.js のシーングラフは機能過多であり、§8の初期JS予算(30KB以下、ただしWebGLは遅延ロードで初期には含まない)に対しても、遅延ロード時点での転送量は小さいほうが望ましい。 OGL はWebGL周りの定型処理だけを薄くラップしており、この用途に対する過不足のなさで選んだ。

  4. なぜ Cloudflare Pages ではなく Workers か

    判断したこと
    ホスティングを Cloudflare Workers(Static Assets)にする。当初予定していた Pages は採用しない。
    検討した選択肢
    • Cloudflare Pagesこのサイトは完全な静的出力なので Pages でも過不足なく動く。設定ファイルすら要らない点はむしろ優れている。ただし Cron Triggers、Queue、Workers Logs、Email Workers、非ルートパスへの配信などが Pages では使えず、後から必要になった時点で移行作業が発生する。
    • Vercel / Netlify などの静的ホスティング配信品質に不満はないが、独自ドメインとDNSを既に Cloudflare で持っているため、事業者を分けると管理点が増える。分ける利点が見つからなかった。
    • GitHub Pages無料で単純だが、エッジ配信の品質と設定の自由度(ヘッダー、リダイレクト、将来の動的処理)で劣る。
    決め手
    静的配信の結果は Pages と Workers で変わらない。差が出るのは『後で動的処理が必要になったとき』だけで、そのとき移行コストを払うくらいなら、等価な今のうちに広いほうを選んでおく。支払う代償は wrangler.jsonc 一枚。

    当初の仕様では 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 の数値も変わらない。選んでいるのは性能ではなく、将来の選択肢の広さである。

    このサイトは「機能を足さないこと」を繰り返し選んできたが、それは動くものを増やさないという意味であって、選べる余地を捨てるという意味ではない。前者はコストを減らすが、後者は将来のコストを増やす。

  5. なぜグローを限定して使うか

    判断したこと
    看板やネオン管など、光源として意味を持つ要素に限ってグローを使う。地の面や文字組み全体には広げない。
    検討した選択肢
    • 当初の原則禁止を維持するネオン街を主題にしながらサイト自身にはネオンがほぼなく、成果物を看板として読ませる表現とも噛み合わなかった。
    • 見出しやカードを含む主要要素をすべて光らせる光の希少性がなくなって定型表現へ戻り、背景が明るくなるぶん文字とのコントラストも落ちる。
    決め手
    禁止したかったのは発光ではなく、意味のない全面装飾だった。光る理由がある場所を選び、光をそこへ集中させれば、当時の暗さを優先する原則とも両立できると判断した。

    制作開始時の 2026 年 1 月 18 日には、グローを原則禁止し、ホバー中の窓セル1つだけを例外にした。制約は守れたが、ネオン街を題材にしたサイトからネオンそのものが消えていた。

    成果物一覧を「1作品 = 1枚の看板」に改めたことで、グローは飾りではなく情報を読ませる光になった。文字の芯を白に寄せ、色は内側・外側のグローと周囲への漏れ光に分ける。この規則は neon.css に集約している。

    解禁後も、光るのは成果物の看板、縦看板、選択中の導線などに限る。多色は使っても同じ強さで全面に広げず、文字が乗る場所では背景の最も明るい画素とのコントラストを測る。

    2026 年 9 月 30 日に、暗さを優先する原則そのものを廃止した。現在は夜の都市を背景として見せながら、作品と文章の読みやすさを優先し、ネオンは世界観を補強するアクセントとして使う。グローを光源として意味のある要素に限る判断は、この方針のもとでも変わらない。

  6. なぜ同名の GitHub リポジトリを改名したか

    判断したこと
    ドメインと同名だった GitHub リポジトリを portfolio-site に改名し、構造化データの sameAs でサイト・GitHub・X を同一人物の資産として明示する。
    検討した選択肢
    • 何もせず、自然に順位が入れ替わるのを待つリポジトリのほうが先に公開されており、github.com というドメイン自体の評価も比較にならない。放置して逆転する見込みが薄い。それ以上に、検索した人が最初に見るのがソースコードのリポジトリになる状態が、ポートフォリオとして本末転倒だった。
    • サイトのタイトルや見出しに mawo.dev を入れて、検索文字列との一致度を上げる検索エンジンに向けて命名を歪める行為で、サイトの名前が名前として意味を失う。順位のために中身を変え始めると、どこまでも変えられてしまう。
    • 被リンクを増やす(ディレクトリ登録、相互リンク)効果を自分で検証できない上、増やしたリンクは後から取り消せない。取り返しのつかない施策を、効果が読めないまま打つ理由がない。
    決め手
    衝突していたのは順位ではなく識別だった。同じ文字列を持つ2つの資産があり、どちらが本人の公式サイトかを示す材料が無い。ならば片方の名前を変えて衝突自体を消し、構造化データで『この3つは同じ人物のものだ』と機械に伝えるのが筋になる。サイトの中身は一文字も変えていない。

    公開直後、「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 の絞り込みも、すべて測った結果として決めてきた。しかし検索順位は自分で決められる指標ではなく、反映までの時間も制御できない。ここに書いたのは打った手であって、結果ではない。外れていたらこの項目に追記する。