技術ノート | AI駆動開発の現場から
このサイト(電脳技巧集団のコーポレートサイト)のブログを、先日WEBマガジン風に作り替えた。一覧性が上がって満足していた——その矢先、主宰の甲斐から一言。「とにかく、動きがもっさり重い」。
速度の不調は、たいてい「重いライブラリのせいだ」と決めつけてしまう。だが今回は、まず計測してから犯人を絞った。結論を先に言うと、犯人は派手な技術ではなく地味な画像だった。ブログ一覧の表示が 約10MB → 約0.3MB、全ページ常駐のロゴが 684KB → 2KB。何をどう疑い、どう直したか——実測の数字つきで残しておく。[1]

01「もっさり重い」と、言われた日
マガジン化で、ブログトップは見違えた。ヒーロー+カテゴリ別のセクションで、記事が一目で見渡せる。一覧性は狙いどおり。
ところが、スクロールがねっとりする。ページの表示にも、わずかな"待ち"を感じる。「もっさり重い」——感覚的だが、的を射た指摘だった。ここで多くの人がやりがちなのが、「アニメーションを減らそう」「WebGLが重いんじゃないか」と、当たりをつけて手を動かすこと。だが、当てずっぽうで直すと、たいてい見当違いの場所を削って時間を溶かす。まず、計測する。
02容疑者を絞る:重いのはJSじゃなかった
最初に、いちばん疑われがちな"重量級ライブラリ"を確認した。package.json を見ると——Three.js も GSAP も Lenis も、入っていない。
これは大きい。3D描画(Three.js)も、重いアニメ(GSAP)も、スムーズスクロール(Lenis)も無い。とくに「スムーズスクロール」は、スクロールに意図的な"ため"を作るので、それ自体が「もっさり」の正体になりがちだが、今回は不在。つまりJSバンドルの重さや、スクロール遅延ライブラリは、犯人ではない。容疑者は3つに絞れた——①画像 ②描画アニメ(canvas) ③CSSのぼかし(blur)。
「無いことを確認する」のは地味だが、調査では一番効く。可能性を一つずつ消すと、残った容疑者に資源を集中できる。逆に、ここを飛ばして「たぶんアニメ」と決め打つと、削っても直らず、また別の場所を当てずっぽうで触る——という消耗戦に入る。最初の5分の確認が、その後の数時間を救う。
03真犯人①:1MBの画像を、300pxで表示していた
画像を数えて、血の気が引いた。public 配下の PNGが計44.5MB・52枚。記事カバーは1枚 800KB〜1.2MBのフルサイズ(1200×630)。しかも、これを <img> タグそのままで読み込んでいた(最適化なし)。
致命的なのは表示サイズとの落差だ。マガジントップはカバーを約14枚並べる。1枚300px幅で見せているのに、ブラウザは毎回1200pxのPNGをまるごとダウンロードして、デコードしている。合計すると1ページで10MB超。これでは、回線が速くても"重い"。スクロールのたびに巨大画像のデコードがCPUを噛む。これが「もっさり」の最大の正体だった。
そして、この手の重さは気づきにくい。開発中は画像がブラウザにキャッシュされているし、手元の回線は速い。「自分の環境では普通に動く」から、問題が見えない。実際に効いてくるのは、初めて訪れた人・モバイル回線・非力な端末——つまり、いちばん大事にしたい相手の環境だ。巨大PNGの生<img>は、エラーも警告も出さずに、静かに体感を削る。派手に壊れないぶん、見過ごされ続ける。

04next/imageで、桁が変わる(832KB→7KB)
直し方は、王道だった。カバーを Next.jsの next/image に通すだけ。これだけで、画像は表示サイズに自動リサイズされ、avif/webpで配信される。設定は next.config に formats:[avif,webp] を足し、各画像に「表示幅(sizes)」と、ファーストビューにはpriorityを渡す。
効果は、桁で変わった。同じカバー画像での実測を表に整理する。
| 形式・配信幅 | サイズ | 削減率 |
|---|---|---|
| 生PNG(原寸) | 832 KB | —(基準) |
| next/image webp・幅384 | 7 KB | 約99%減(カード表示幅) |
| webp・幅640 | 18 KB | 約98%減 |
| webp・幅828 | 28 KB | 約97%減 |
※ 削減率は実測サイズ(生PNG 832KB基準)からの計算値。
仕組みも気が利いている。next/imageは、端末の解像度に合わせた複数サイズの候補(srcset)を自動で用意し、画面に入る直前まで読み込みを遅らせる(遅延読み込み)。だからファーストビューの外にあるカードは、スクロールして初めて、しかも端末にぴったりのサイズだけ取得される。唯一の注意点は sizes(その画像が実際に表示される幅)を正しく書くこと——ここを大きく書きすぎると、過大なサイズを取りに行って効果が薄れる。リード(大きい)・カード(中)・サムネ(小)で、それぞれ実寸に合わせて指定した。
結果、マガジントップ全体では画像の転送量が 約10MB → 約0.3MB。一覧の全カバーが最適化エンドポイント経由になり、生PNGの直リンクはゼロになった。見た目は1ピクセルも変わらないまま、体感だけが軽くなる。コードの差分は小さいのに、効果は桁で出る——いちばん費用対効果の高い一手だった。
05全ページに効く一手:常駐ロゴ684KB→2KB
もう一つ、地味だが効いた発見がある。サイトの全ページに常駐するチャットの起動ボタン。そこに表示しているロゴマークが、684KBのPNGを、20〜24pxで表示するために生<img>で読んでいた。
常駐パーツの怖さは、どのページを開いても必ず読まれること。トップでも、ブログでも、会社概要でも、毎回684KB。これを next/image 化したら、avifで2.3KB(表示48px相当)に落ちた。サイト全体に波及する無駄は、最優先で潰す——これは画像最適化の鉄則だ。1枚のアセットでも、全ページ常駐なら影響は全ページ分になる。逆に言えば、ヘッダーのロゴ・ファビコン・共通アイコンのような「どこにでも出るもの」は、最初から最適化しておく価値が一番高い。記事ごとのカバーより、まず"常駐枠"を疑うのが近道だ。
06真犯人②:canvasの「影」と「総当たり」
容疑者の2つ目、トップの粒子ネットワーク(背景で点が線で繋がるアニメ)。ここに、canvasの二大コストが潜んでいた。
- 粒子ごとの影(
shadowBlur)。点に光彩を出すため、毎フレーム・粒子の数だけ影を描いていた。shadowBlurはcanvasで最も重い処理の一つ。 - 総当たりの線(O(n²))。近い点どうしを線で結ぶのに全ペアを判定し、しかも同じ線を2回引いていた。
直し方:影をやめて単色の点に(光彩はわずかに失われるが、ほぼ見分けがつかない)。線は各ペアを1回だけ引くよう片側走査に(計算量と描画を半減)。見た目はほぼそのまま、フレームあたりの仕事量だけが大きく減る。これでスクロール中の引っかかりが取れた。[2]
ポイントは、「画面外で止める」だけでは足りなかったこと。粒子アニメは元から、画面に入っていない間や別タブのときは停止していた。それでも「もっさり」した。つまり問題は"動いている間の1フレームが重すぎる"ことにあった。停止制御(いつ動かすか)と、フレーム内の処理量(動いている間どれだけ働くか)は別の軸だ。前者を入れても後者が重ければ、表示中のスクロールは重いままになる。両方を見る必要がある。
07真犯人③:スクロールを削る、ぼかし
最後の容疑者は、CSSの backdrop-filter: blur(背景をすりガラス状にぼかす効果)。カバー画像の上に重ねたカテゴリの小さなラベルに、これが付いていた。
1枚なら問題ない。だがマガジン一覧ではカバーが14枚。つまり14枚分のぼかし合成が、スクロールのたびにGPUで再計算される。これがスクロールの滑らかさを地味に削っていた。直し方は単純で、ぼかしをやめて、単色の半透明背景に。不透明度を少し上げれば可読性は保てる。スクロール時の再描画コストはゼロになった。
08締め:「もっさり」の切り分けと、職人の責任
今回の学びは、手順そのものだ。「もっさり重い」と言われたら、次の順で疑う。
- ① まず重量級ライブラリの有無を確認(Three/GSAP/Lenis等)。無ければJSバンドルやスクロール遅延は除外できる。
- ② 画像。総量・生
<img>の有無・next.configの最適化設定。たいていここが最大の犯人。 - ③ 連続アニメ(canvas/
requestAnimationFrame)。影と総当たりを疑う。 - ④
backdrop-filterが「スクロールで何枚も再描画される場所」にないか。 - そして計測は本番ビルドで。開発サーバー(最適化オフ)で測ると判断を誤る。
この順番が大事なのは、当てずっぽうの最適化は、たいてい間違った場所を削るからだ。「アニメが重そう」と削っても、実は画像が9割なら、苦労のわりに体感は変わらない。計測してボトルネックを特定し、効く順に潰す。今回も、いちばん地味な画像が、いちばん効いた。
「サイトが速い」ことは、派手な機能ではない。だが、訪れた人の時間を奪わないという、いちばん基本的な誠実さだ。私たちは「サイト自体が、開発力の動く証拠」だと考えている。だからこそ、自社のサイトを、まず速くする。そして数字を、隠さず開示する——10MB→0.3MB、684KB→2KB。これがCAGの透明性の、ささやかな実践だ。

ナイーブな実装 vs 職人の実装。「もっさり」の差は、たいていこの積み重ねに出る。
| 観点 | ナイーブな実装 | 職人の実装(CAG) |
|---|---|---|
| 画像 | 生<img>でフルサイズPNG | next/imageで表示幅へ自動リサイズ+avif/webp |
| カバー1枚 | 832 KB | 7 KB(約99%減) |
| 常駐アセット | 全ページで684KBを毎回 | 最優先で潰す → 2 KB |
| canvas | 粒子毎shadowBlur+O(n²) | 影なし+各ペア1回(i<j) |
| blur | スクロールで14枚再描画 | 単色化で再描画コスト0 |
| 直し方 | 当てずっぽうで削る | 計測→切り分け→本番ビルドで検証 |
※ 数値は本サイト(Next.js + Vercel)での実測(2026-06時点・v0)。`next build`→`next start` 環境で計測。条件により前後する。
脚注
- 本記事は、当サイトのブログをWEBマガジン化した直後に「もっさり重い」と感じた実体験から、原因を計測で特定し改善した一次記録。固有の内部パス・設定の全文は省いた。
- canvasの粒子アニメは、画面内にある間だけ描画し、オフスクリーンや非表示タブでは停止する実装(IntersectionObserver+visibilitychange)+reduced-motion対応は元から入れていた。今回はそれに加え「画面内にある間の1フレームの仕事量」を削った。
「自分たちのサイト、なんか重い気がする」——その正体、計測で突き止めます。
表示速度の調査・改善も、AI駆動で速く。まずは相談から——問い合わせは、AIがその場でお応えします。
無料で相談する →








