React, Vue.js
投稿日:

<画像読み込み負荷を徹底管理>Next/Nuxt Imageの最適化

こんにちは、株式会社ファストコーディングのフルスタックエンジニア、独身貴族Fireです。

つい先週、趣味のバイクツーリングで山間部に出かけた時のことです。休憩がてら道の駅でスマホを開き、近くの観光スポットを検索していたのですが、画像が多いサイトがなかなか表示されず、イライラした経験がありました。特にメインビジュアルがいつまでも読み込まれないと、「このサイト、本当に見る価値があるのか?」と疑問を持ってしまいます。

ブログの書き出しを書くのって本当に難しいなって思います。でも嘘は書いてないですよ…本当に重たいページでした。

Webパフォーマンスの世界では、この「メインコンテンツが表示されるまでの時間」をLCP(Largest Contentful Paint)と呼び、Googleのコアウェブバイタルの重要指標の一つとして位置づけられています。特にEC、不動産、メディアサイトなど、画像が主役のサイトでは、LCPの大半が画像の読み込み時間に依存します。

今回は、Next.jsとNuxtという2大フレームワークの画像最適化機能を使って、LCPを確実に短縮し、CLSも抑制する実装手法を、フロントエンドエンジニアの視点から詳しく解説します。

画像パフォーマンスの現状と課題

特にECサイトや不動産ポータル、メディアサイトでは、商品画像、物件写真、記事のアイキャッチ画像がコンテンツの中心です。しかし、画像の扱い方次第でユーザー体験は大きく変わります。そして多くのサイトでは(いまだに・・・)以下のような単純な実装が行われています。

悪い例: 従来の(HTMLそのまま)実装

<img src="/images/product-large.jpg" alt="商品画像" />

単にimgタグを書くだけで画像が出るからHTMLはすごいんですが、この実装はパフォーマンスの問題があります。たとえば、

  • 解像度の最適化がされていない: デスクトップもモバイルも同じ大きな画像を配信
  • 優先度制御がない: すべての画像が同じ優先度で読み込まれる
  • レイアウトシフトが発生: 画像が読み込まれた瞬間にレイアウトがずれる(CLS悪化)
  • 帯域の無駄遣い: モバイル環境で不必要に大きな画像をダウンロード

実際に私たちが運用を担当しているとあるECサイトでは、当初、商品一覧ページのLCPが4.2秒にも達していました。これはGoogleの推奨値(2.5秒以下)を大きく上回る数値です。

Next.js/Nuxtの画像最適化機能

Next.jsとNuxtには、それぞれ強力な画像最適化コンポーネントを提供しています。具体的にはsrcset、fetchpriority、先読み、アスペクト比制御を自動的に、あるいは簡単に実装できます。

Next.js Imageコンポーネントによる自動最適化

Next.jsの<Image>コンポーネントを使えば、画像をいろいろと自動的に最適化してくれます。

良い例: Next.js Imageコンポーネントの基本実装

import Image from 'next/image'

export default function ProductCard({ product }) {
  return (
    <div className="product-card">
      <Image
        src={product.imageUrl}
        alt={product.name}
        width={800}
        height={600}
        sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 800px"
        quality={85}
      />
    </div>
  )
}

自動的に最適化されること:

  • srcsetの自動生成: デバイスに応じた複数サイズの画像を自動生成(これが一番便利)
  • WebP/AVIF変換: ブラウザサポートに応じて最適なフォーマットに自動変換
  • 遅延読み込み: デフォルトでビューポート外の画像は遅延読み込み
  • CLS防止: width/heightによる領域確保で自動的にCLS抑制

Next.js Image コンポーネントの fetchpriority で LCP 最適化

自動的に遅延読み込みされると問題が起きる場合がありますね。たとえばページアニメーションで使っていたり、マウスのホバーやキー操作で表示されるような要素です。こういった要素についてはpriority属性を指定します。これによりfetchpriority="high"が自動設定され、遅延読み込みもスキップされます。

良い例: LCP要素への priority 指定

import Image from 'next/image'

export default function Hero() {
  return (
    <div className="hero">
      <Image
        src="/images/hero.jpg"
        alt="メインビジュアル"
        width={1920}
        height={1080}
        sizes="100vw"
        priority  // ← fetchpriority="high" & 遅延読み込み無効化
        quality={90}
      />
    </div>
  )
}

ただし、過剰にこれを使うのは結局意味がなくなりますので(画像がまた即時読み込みになるだけなので)画像の解像度にもよりますが、1ページに〜3個ぐらい、という程度の、本当に「優先」する画像として定義しましょう。

Next.js Image – preload での先読み最適化

Next.js 15以降では、<link rel="preload">も自動的に出力されます。さらに細かく制御したい場合は、next/headで明示的に設定できます。

良い例: 手動preload設定(Next.js)

import Head from 'next/head'
import Image from 'next/image'

export default function ProductPage({ product }) {
  return (
    <>
      <Head>
        <link
          rel="preload"
          as="image"
          href={product.heroImage}
          imageSrcSet={`${product.heroImage}?w=640 640w, ${product.heroImage}?w=1200 1200w`}
          imageSizes="100vw"
        />
      </Head>
      <Image
        src={product.heroImage}
        alt={product.name}
        width={1200}
        height={800}
        priority
      />
    </>
  )
}

Nuxt Image コンポーネントによる最適化

Nuxtでは@nuxt/imageモジュールを使います。

良い例: Nuxt Image基本実装

<template>
  <div class="product-card">
    <NuxtImg
      :src="product.imageUrl"
      :alt="product.name"
      width="800"
      height="600"
      sizes="sm:100vw md:50vw lg:800px"
      :quality="85"
    />
  </div>
</template>

<script setup>
const props = defineProps({
  product: Object
})
</script>

Nuxt版 Imageコンポーネントの特徴:

  • 自動的なsrcset生成: サイズに応じた複数画像を自動生成
  • プロバイダー対応: Cloudinary、Imgix、Vercelなど主要CDNに対応
  • 遅延読み込み: デフォルトでloading="lazy"
  • プレースホルダー: ぼかし画像や色で読み込み中を表現

Nuxt Image コンポーネントの fetchpriority と preload

Nuxtでも、LCP要素にはpreloadとfetchpriorityを設定できます。

良い例: Nuxt Imageでの優先読み込み

<template>
  <div class="hero">
    <NuxtImg
      src="/images/hero.jpg"
      alt="メインビジュアル"
      width="1920"
      height="1080"
      sizes="100vw"
      preload  
      fetchpriority="high"
      :quality="90"
    />
  </div>
</template>

nuxt.config.ts でpreload設定:

export default defineNuxtConfig({
  image: {
    preload: {
      formats: ['webp', 'avif']
    }
  }
})

実際のプロジェクトでの効果

弊社で担当しているお客様のECサイトの商品一覧ページで、Next.js Imageコンポーネントを導入したところ、以下の改善が得られました。

指標改善前改善後改善率
LCP4.2秒1.8秒57%改善
CLS0.250.0580%改善
転送量(モバイル)3.2MB1.1MB66%削減

実装時のベストプラクティス

基本的にはコンポーネントを使うだけで改善されるのですが、使う時のベストプラクティスはこうなります。

1. priority は LCP 要素のみ

  • ヒーロー画像、ファーストビューの主要画像のみに使用
  • 1ページあたり1-2個まで

2. sizes 属性を適切に設定

  • ビューポート幅に応じた実際の表示サイズを指定
  • モバイル、タブレット、デスクトップで異なる場合は細かく設定

3. quality は用途に応じて調整

  • 商品画像・物件写真: 85-90
  • 背景画像: 75-80
  • アイコン・UI画像: 90-95

4. CDN・画像プロバイダーの活用

  • Next.js: Vercel Image Optimization、Cloudinary
  • Nuxt: Cloudinary、Imgix、ImageKit

よくある落とし穴

過剰なpreload・priority:全ての画像にpriorityを指定すると、ブラウザがどれを優先すべきか判断できず、逆にパフォーマンスが悪化します。

width/heightの未指定:アスペクト比が不明だと、画像読み込み時にレイアウトシフトが発生しCLSが悪化します。必ずwidth/heightを指定しましょう。

モバイルで高画質すぎる設定quality={95}など高画質設定をモバイルでも使うと、転送量が増えLCPが悪化します。用途に応じて調整しましょう。

そしてありがちなのが「使いすぎてしまう」という問題です。特に画質設定については、毎回お客様と(主にデザイナ様と)議論になりますが、レティーナディスプレイを意識しすぎて画質を上げることで、ページ全体のパフォーマンスは落ちることになりますので、結果的に離脱を増やすことになります。見た目やデザインと、表示速度のパフォーマンスは、相反する時が多いので注意してください。

さくっと画像コンポーネントで対応してしまいましょう

画像主導のWebサイトでは、Next.jsやNuxtの画像最適化機能を活用することで、HTMLベースの手動実装よりも圧倒的に簡単かつ効果的にLCPとCLSを改善できます。押さえておきたいポイントは以下の3つです。

  • Next.js/Nuxt Imageコンポーネントで自動最適化: srcset、WebP/AVIF変換、遅延読み込みを自動化
  • priorityでLCP要素を優先読み込み: ヒーロー画像やファーストビュー画像に限定
  • width/height指定でCLS抑制: アスペクト比を保持し、レイアウトシフトを防止

実際のプロジェクトでは、LCPが57%改善、CLSが80%改善、モバイル転送量が66%削減という結果が得られました。

株式会社ファストコーディングでは、こういったNext.js/Nuxtを活用した画像最適化やLCP改善の実装サポートを行っています。EC、不動産、メディアサイトなど、画像が多いサイトのパフォーマンス改善にお困りの際は、ぜひお問い合わせフォームからご相談ください。