Slide 1

Slide 1 text

本当の敵は思い込み? ZOZOTOWNのパフォーマンス チューニング事例 株式会社ZOZO ZOZOTOWN開発本部1部 WEBフロントエンドブロック 佐藤 仁 Copyright © ZOZO, Inc. 1

Slide 2

Slide 2 text

© ZOZO, Inc. 株式会社ZOZO ZOZOTOWN開発本部1部 WEBフロントエンドブロック 佐藤 仁 24卒 2年目のフロントエンドエンジニアです 休日は蕎麦打ち教室に通ったり、ピザを焼いたりしてい ます 2

Slide 3

Slide 3 text

© ZOZO, Inc. https://zozo.jp/ 3 ● ファッションEC ● 1,700以上のショップ、11,000以上のブランドの取り扱い ● 常時107万点以上の商品アイテム数と毎日平均2,700点以上の新着 商品を掲載(2025年12月末時点) ● ブランド古着のファッションゾーン「ZOZOUSED」や コスメ専門モール「ZOZOCOSME」、シューズ専門ゾーン 「ZOZOSHOES」、ラグジュアリー&デザイナーズゾーン 「ZOZOVILLA」を展開 ● 即日配送サービス ● ギフトラッピングサービス ● ツケ払い など

Slide 4

Slide 4 text

© ZOZO, Inc. 4 注意 ● 本登壇で紹介したチューニングの全てが本番環境に反映済みではありません ○ 改修予定のものが含まれることを予めご了承下さい

Slide 5

Slide 5 text

© ZOZO, Inc. 5 アジェンダ 1. 背景 2. 結果 3. 問題の切り分け 4. 制約 5. PCのチューニング 6. SPのチューニング 7. まとめ

Slide 6

Slide 6 text

© ZOZO, Inc. 6 背景 2025年6月以降、検索結果画面のSERPsランキングが下落傾向⤵ サービスグロース部から検索結果画面のCore Web Vital改善を依頼される SERPs: Search Engine Results Pages Googleなどの検索エンジンでキーワード検索した際に表示される結果画面

Slide 7

Slide 7 text

© ZOZO, Inc. 7 CWVの指標について ● LCP (Largest Contentful Paint) ○ 知覚される読み込み速度を測定するための重要な、安定した Core Web Vitals 指標です。ページの読み込みタイム ライン上で、ページのメイン コンテンツが読み込まれた可能性が高いポイントをマークします。 ● TTFB (Time To First Byte) ○ リソースのリクエストからレスポンスの最初のバイトが到着するまでの時間 ● FCP (First Contentful Paint) ○ ユーザーがページに初めて移動してから、ページのコンテンツのいずれかの部分が画面上にレンダリングされるまで の時間 ● TBT (Total Blocking Time) ○ FCPの後にメインスレッドが入力の応答性を妨げるほど長くブロックされていた合計時間 https://web.dev/?hl=ja

Slide 8

Slide 8 text

© ZOZO, Inc. 8 結果 PCのLCPを3.3s → 0.7sに改善

Slide 9

Slide 9 text

© ZOZO, Inc. 9 結果 appチャンクのGzipped sizeを238.83KB→169.14KBに削減

Slide 10

Slide 10 text

© ZOZO, Inc. 10 問題の切り分け ● ページごとの計測結果は? ● 商品数の違いはあるか? どのページでも、商品数に関係なく LCPは悪い

Slide 11

Slide 11 text

© ZOZO, Inc. 11 問題の切り分け ● LCPの種類を網羅 ● LCPの内訳 バナーと商品画像がLCP、TTFBとリソースの読み込みが遅れ気味

Slide 12

Slide 12 text

© ZOZO, Inc. 12 問題の切り分け ● SPではTBTも悪い ○ scriptの読み込みがボトルネックとなっている

Slide 13

Slide 13 text

© ZOZO, Inc. 13 問題の切り分け ● Waterfallを確認 scriptの読み込み後に商品画像が読み 込まれている scriptにはNext.jsが分割したチャン ク、計測系のThrid-Party Scriptが含 まれる

Slide 14

Slide 14 text

© ZOZO, Inc. 14 制約 ● ドキュメントのCDNキャッシュはできない ○ 検索結果のパーソナライズを実施しており、常に最新の結果を返す必要がある ● Next.jsはPages Routerを使用 TTFBの改善は期待できない

Slide 15

Slide 15 text

© ZOZO, Inc. 15 制約 ● GTM、KARTE、Datadogといった計測scriptの読み込みは遅延できない ○ page_viewといったログにリアルタイム性が求められる ○ partytownを使いたいが、メジャーバージョンではないので実運用は難しい ● 新しいWeb API、プロパティはブラウザ互換性のために使用できない ○ requestIdleCallback ○ content-visibility

Slide 16

Slide 16 text

© ZOZO, Inc. 16 PCのチューニング ● 元々商品画像はSSRされているものだと勘違いをしていた ○ SSRできているならHTMLレスポンス時にimg要素は存在する ○ scriptの読み込みより前、もしくは並列に商品画像のリクエストが行われていないのはな ぜか...?

Slide 17

Slide 17 text

© ZOZO, Inc. 17 PCのチューニング 画像コンポーネントではloading=”eager”が ないとInterSectionObserverで遅延読み込 みをするようになっていた Web APIはSSR上でレンダリングできないた め、Hydration完了を待つ必要があった const enableRender = useMemo(() => { if (loading === 'eager') { return true } return isIntersecting }, [loading, isIntersecting]) {enableRender && (

Slide 18

Slide 18 text

© ZOZO, Inc. 18 PCのチューニング 最初の3枚だけloading=”eager”を付与すると... ドキュメントの読み込み完了後、HTML解析時点で img要素が存在するため早期にリクエストが走る

Slide 19

Slide 19 text

© ZOZO, Inc. 19 PCのチューニング これだけでは改善に至らず LCPの画像がSSRをした画像ではなく、遅延読み込みをした画像になってしま う... 商品画像は同じサイズなはずなのにどうして? > 同じサイズの 2 つの画像がレンダリングされた場合、最初の画像が LCP 要素と見なされます。LCP 要素 は、LCP 候補が現在の LCP 要素よりも大きい場合にのみ更新されます。 https://web.dev/articles/carousel-best-practices?hl=ja

Slide 20

Slide 20 text

© ZOZO, Inc. 20 PCのチューニング WebPerf SnippetsのLCP Trailを改修したものでLCPを調べると rectの高さと幅は同じなのにsizeがわずかに異なることが判明 ● SSRした画像 ○ rect.area:23527.219 ○ LargestContentfulPaint.size:23514 ● 遅延読み込みした画像 ○ rect.area:23527.219 ○ LargestContentfulPaint.size:23517(+3) https://webperf-snippets.nucliweb.net/CoreWebVitals/LCP-Trail https://developer.mozilla.org/en-US/docs/Web/API/LargestContentfulPaint/size?utm_source=chatgpt.com

Slide 21

Slide 21 text

© ZOZO, Inc. 21 PCのチューニング LCPのsizeはintersectionRectの高さと幅から求められる intersectionRect:imageElementをターゲット、viewport をルートとして使用 して交差四角形アルゴリズムによって返される値 =画面内に表示されている画像のサイズを求めている https://w3c.github.io/largest-contentful-paint/#sec-report-largest-contentful-paint https://w3c.github.io/largest-contentful-paint/#sec-report-largest-contentful-paint

Slide 22

Slide 22 text

© ZOZO, Inc. 22 PCのチューニング 遅延読み込みされた画像が描画されるまでintersectionRectが確定しない 下記スタイルで高さと幅が決まっている 整数値のwidth/height属性に付与することでレイアウトを先に済ませる grid-template-columns: repeat(5, minmax(0, 1fr)); gap : 1.9%;

Slide 23

Slide 23 text

© ZOZO, Inc. 23 PCのチューニング SSRした商品画像が安定的にLCPとして検出されるようになる 3.3s → 0.7s

Slide 24

Slide 24 text

© ZOZO, Inc. 24 SPのチューニング SPではPCと比べてCPU性能が低い 同じJavaScriptでも処理に時間がかかり、TBTが悪化してしまう どのページでも共通で読み込まれるappチャンク のサイズを減らし、少しでも負荷を減らす

Slide 25

Slide 25 text

© ZOZO, Inc. 25 SPのチューニング ● appチャンクのバンドルサイズを 計測 ○ Gzipped sizeで238.83KB ● CarouselとapiClient、これらは必 ずしも全てのページで読み込むも のではない

Slide 26

Slide 26 text

© ZOZO, Inc. 26 SPのチューニング ● CarouselはSPのFooterに使用されている ので、appチャンクに含まれてしまう ● Carousel内部のSwiperやGSAPがappチャ ンクを膨らませる

Slide 27

Slide 27 text

© ZOZO, Inc. 27 SPのチューニング ● 採用バナーが複数枚の時はCarouselが必要だが、1枚の時のUIは単純なの で自前で実装をする ○ 2枚以上でナビゲーションが表示される ● バナーが複数枚の時はdynamic importすることでappチャンクから外す

Slide 28

Slide 28 text

© ZOZO, Inc. 28 SPのチューニング ● apiClientはopenapi-typescript-code-generatorから生成される ● ClassApiClient.generatorを使っているが、クラスが全てのメソッドを一 つのオブジェクトにまとめてしまうのでtree-shakingが効きにくい ○ 加えてリプレイスが進むにつれてエンドポイントが増え続け、appチャンクも膨らみ続け る ● FunctionalApiClient.generatorを使い、tree-shakingが効くようにする https://github.com/Himenon/openapi-typescript-code-generator

Slide 29

Slide 29 text

© ZOZO, Inc. 29 SPのチューニング 238.83KB→169.14KB 約30%ほど削減

Slide 30

Slide 30 text

© ZOZO, Inc. 30 SPのチューニング ● 採用バナー画像、関連サービスの画像はFooterに存在するがSSRされてい る ○ 遅延読み込みを有効にさせる

Slide 31

Slide 31 text

© ZOZO, Inc. 31 私がしていた思い込み ● getServerSidePropsにおけるSSRの境界が曖昧だった ○ 商品画像は既にSSRされているものかと思っていた ○ SSRされていればHTMLのプレビューにimg要素が存在している ○ HTML解析時に画像のリクエストも飛ぶ ● LCPの算出においてrectの面積が同じであれば同じサイズとして扱われると 思っていた ○ 見かけに騙されず、snippetで実測値から判断をする

Slide 32

Slide 32 text

© ZOZO, Inc. 32 思い込みと戦うには ● とりあえずベストプラクティスに則る ○ First Viewに含まれる画像は早期にリクエストを送る ○ 画像にはwidth/height属性を付与する ● AIと壁打ちする ○ Chrome DevTools MCPやdevtools内のAI アシスト機能を使う ○ コードを読み込ませるだけで色々提案してくれるので便利 ■ loading=”eager”くらいなら提案してくれる

Slide 33

Slide 33 text

© ZOZO, Inc. 33 思い込みと戦うには ● 実測値ベースで考察する ○ WebPerf Snippets便利でおすすめ ● 他のページと比較をしてみる ● 正しい知識を身につける ○ 仮説の幅を広げ、確度を高める

Slide 34

Slide 34 text

No content