コンテンツにスキップ

Chapter 3: フォントと画像の最適化

Chapter 2 でスタイルの基礎を整えたので,ここからは表示速度と体験を意識した最適化に踏み込みます。カスタムフォントやヒーロー画像の扱いは,プロダクション品質を左右する大事な要素です。


この章で身につくこと

  • next/font を使ってビルド時に最適化されたフォントを読み込む
  • next/image で画面サイズに応じたレスポンシブ画像を提供する
  • フォントと画像がレイアウトシフトに与える影響を理解する

フォント最適化の背景

カスタムフォントを追加すると見た目はリッチになりますが,読み込みの遅延によって Cumulative Layout Shift(CLS) が発生し,コンテンツがガタつくことがあります。next/font を使うとビルド時にフォントファイルを取得し,他の静的アセットと同じ場所でホストしてくれるため,追加のネットワークリクエストが不要になり表示が安定します。

alt text


Step 1. プライマリフォントを定義する

/app/ui フォルダに fonts.ts を作成し,Google Fonts から Inter (というフォント名)を読み込みます。サブセットは 'latin' を指定します。

/app/ui/fonts.ts
import { Inter } from 'next/font/google';

export const inter = Inter({ subsets: ['latin'] });

Step 2. レイアウトへ適用する

続いて /app/layout.tsxinter を読み込み,<body> にクラスを付与します。Tailwind の antialiased を併用するとフォントレンダリングが滑らかになります。

/app/layout.tsx
import '@/app/ui/global.css';
import { inter } from '@/app/ui/fonts';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={`${inter.className} antialiased`}>{children}</body>
    </html>
  );
}

ブラウザの DevTools を開くと,<body>InterInter_Fallback が適用されているのが確認できます。


セカンダリフォントを追加する

特定の要素だけ別フォントを使いたいケースもよくあります。ここでは見出しに Lusitana を適用しましょう。

  1. fonts.tsLusitana を追加し,必要なウェイト(400700)を指定します。利用可能なウェイトは TypeScript の補完や Google Fonts のページ で確認できます。

    /app/ui/fonts.ts
    import { Inter, Lusitana } from 'next/font/google';
    
    export const inter = Inter({ subsets: ['latin'] });
    
    export const lusitana = Lusitana({
      weight: ['400', '700'],
      subsets: ['latin'],
    });
    
  2. /app/page.tsxlusitana を読み込み,本文の段落へ適用します。

    /app/page.tsx
    import AcmeLogo from '@/app/ui/acme-logo';
    import { ArrowRightIcon } from '@heroicons/react/24/outline';
    import Link from 'next/link';
    import { lusitana } from '@/app/ui/fonts';
    
    export default function Page() {
      return (
        // ...
        <p
          className={`${lusitana.className} text-xl text-gray-800 md:text-3xl md:leading-normal`}
        >
          <strong>Welcome to Acme.</strong> This is the example for the{' '}
          <a href="https://nextjs.org/learn/" className="text-blue-500">
            Next.js Learn Course
          </a>
          , brought to you by Vercel.
        </p>
      );
    }
    
  3. これまではエラー防止のためコメントアウトされていた <AcmeLogo />Lusitana フォントを使うので,アンコメントして表示しておきましょう。

    /app/page.tsx
    export default function Page() {
      return (
        <main className="flex min-h-screen flex-col p-6">
          <div className="flex h-20 shrink-0 items-end rounded-lg bg-blue-500 p-4 md:h-52">
            <AcmeLogo />
            {/* ... */}
          </div>
        </main>
      );
    }
    

これでメインとサブの 2 種類のフォントが揃いました。


画像最適化の考え方

/public フォルダに置いた画像は <img> でも表示できますが,レスポンシブ対応や遅延読み込み,モダンフォーマットへの変換などを自前で行う必要があり,パフォーマンスチューニングの手間が増えます。next/image を使うとこれらを自動化でき,CLS の抑制にもつながります。


Step 3. <Image> コンポーネントを使う

/public には hero-desktop.pnghero-mobile.png が用意されています。まずはデスクトップ用の画像を追加しましょう。

/app/page.tsx
import AcmeLogo from '@/app/ui/acme-logo';
import { ArrowRightIcon } from '@heroicons/react/24/outline';
import Link from 'next/link';
import { lusitana } from '@/app/ui/fonts';
import Image from 'next/image';

export default function Page() {
  return (
    {/* Add Hero Images Here */}
    <div className="flex items-center justify-center p-6 md:w-3/5 md:px-28 md:py-12">
      <Image
        src="/hero-desktop.png"
        width={1000}
        height={760}
        className="hidden md:block"
        alt="Screenshots of the dashboard project showing desktop version"
      />
    </div>
  );
}

widthheight は表示サイズではなく元画像の実寸です。縦横比を指定すると読み込み時のレイアウトシフトが抑えられます。

<Image> には以下のような最適化が標準で備わっています。

  • ビューポートに入るまで画像を遅延読み込み
  • デバイス幅に応じたサイズを自動生成
  • 対応ブラウザには WebP / AVIF といった軽量フォーマットを配信
  • 読み込み中のスペースを確保し CLS を回避

モバイル版のヒーロー画像を切り替える

続いてモバイル専用の画像を追加します。

  • 画像: /public/hero-mobile.png
  • 560px、高さ 620px
  • モバイルでは表示、デスクトップでは非表示(block md:hidden
/app/page.tsx
import AcmeLogo from '@/app/ui/acme-logo';
import { ArrowRightIcon } from '@heroicons/react/24/outline';
import Link from 'next/link';
import { lusitana } from '@/app/ui/fonts';
import Image from 'next/image';

export default function Page() {
  return (
    // ...
    <div className="flex items-center justify-center p-6 md:w-3/5 md:px-28 md:py-12">
      {/* Add Hero Images Here */}
      <Image
        src="/hero-desktop.png"
        width={1000}
        height={760}
        className="hidden md:block"
        alt="Screenshots of the dashboard project showing desktop version"
      />
      <Image
        src="/hero-mobile.png"
        width={560}
        height={620}
        className="block md:hidden"
        alt="Screenshot of the dashboard project showing mobile version"
      />
    </div>
  );
}

DevTools のレスポンシブモードで表示を切り替え,正しく画像が変わっているか確認しましょう。


さらに学ぶ

これでフォントと画像の最適化が完了しました。次章ではレイアウトとルーティングを深掘りしていきます。