Documentation Next.js

はじめに

現代のWeb開発では、スマートフォン、タブレット、デスクトップPCなど、多様なデバイスに対応したUIを構築することが必須となっています。レスポンシブデザインとは、画面サイズに応じてレイアウトやスタイルを動的に調整し、あらゆるデバイスで最適な表示を実現する設計手法です。

この記事では、Next.jsプロジェクトでレスポンシブデザインを効果的に実装するためのベストプラクティスを解説します。具体的には以下の内容を扱います。

  • メディアクエリを使った画面サイズ別のスタイル適用
  • FlexboxCSS Gridによるレスポンシブレイアウト
  • next/imageコンポーネントによる画像の最適化
  • Tailwind CSSを活用した効率的なレスポンシブ設定

レスポンシブデザインの基本概念

ブレイクポイントとは

ブレイクポイントとは、レイアウトが切り替わる画面幅の境界値のことです。一般的なブレイクポイントは以下の通りです。

デバイス画面幅
スマートフォン〜767px
タブレット768px〜1023px
デスクトップ1024px〜

これらの値はプロジェクトによって異なりますが、主要なCSSフレームワークでは似たような値が採用されています。

モバイルファースト vs デスクトップファースト

レスポンシブデザインには2つのアプローチがあります。

  • モバイルファースト: 小さい画面向けのスタイルを基本とし、min-widthで大きな画面向けのスタイルを追加
  • デスクトップファースト: 大きな画面向けのスタイルを基本とし、max-widthで小さな画面向けのスタイルを追加

現在はモバイルファーストが主流です。モバイルユーザーの増加に対応し、パフォーマンス面でも有利なためです。

メディアクエリを使ったスタイル適用

メディアクエリ(Media Query)は、画面サイズに応じて異なるCSSを適用するための仕組みです。CSS Modulesやグローバルスタイルで使用できます。

基本的なメディアクエリの書き方

/* モバイルファースト: 基本スタイル(スマートフォン向け) */
.container {
  display: block;        /* 縦並びレイアウト */
  padding: 16px;
  max-width: 100%;
}

/* タブレット向け(768px以上) */
@media (min-width: 768px) {
  .container {
    display: flex;       /* 横並びレイアウトに変更 */
    gap: 24px;           /* 要素間の間隔 */
    padding: 24px;
  }
}

/* デスクトップ向け(1024px以上) */
@media (min-width: 1024px) {
  .container {
    max-width: 1200px;   /* 最大幅を制限 */
    margin: 0 auto;      /* 中央配置 */
    padding: 32px;
  }
}

Next.jsコンポーネントでの使用例

import styles from '../styles/layout.module.css';

interface ResponsiveLayoutProps {
  children: React.ReactNode;
}

export default function ResponsiveLayout({ children }: ResponsiveLayoutProps) {
  return (
    <div className={styles.container}>
      {children}
    </div>
  );
}

FlexboxとGridを使ったレスポンシブレイアウト

Flexboxによるレイアウト

Flexboxは、一次元(行または列)のレイアウトに適した手法です。ナビゲーションバーやカードの横並びなどに最適です。

/* カードコンテナ */
.cardContainer {
  display: flex;
  flex-wrap: wrap;           /* 画面幅に応じて折り返し */
  gap: 16px;                 /* カード間の間隔 */
  justify-content: center;   /* 中央揃え */
}

/* 各カード */
.card {
  flex: 1 1 300px;           /* 最小幅300px、伸縮可能 */
  max-width: 400px;          /* 最大幅を制限 */
  padding: 20px;
  border-radius: 8px;
  background-color: #f5f5f5;
}

/* タブレット以上で3列表示 */
@media (min-width: 768px) {
  .card {
    flex: 0 1 calc(33.333% - 16px);  /* 3列に固定 */
  }
}
import styles from '../styles/flex-layout.module.css';

interface CardProps {
  title: string;
  description: string;
}

export default function CardGrid({ cards }: { cards: CardProps[] }) {
  return (
    <div className={styles.cardContainer}>
      {cards.map((card, index) => (
        <div key={index} className={styles.card}>
          <h3>{card.title}</h3>
          <p>{card.description}</p>
        </div>
      ))}
    </div>
  );
}

CSS Gridによるレイアウト

CSS Gridは、二次元(行と列の両方)のレイアウトに適した手法です。複雑なグリッドレイアウトやダッシュボードに最適です。

/* auto-fitとminmaxを使った自動調整グリッド */
.gridContainer {
  display: grid;
  /* 最小200px、最大1fr(利用可能な幅を等分)で自動配置 */
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 20px;
  padding: 20px;
}

/* グリッドアイテム */
.gridItem {
  background-color: #e0e0e0;
  padding: 16px;
  border-radius: 4px;
  /* アスペクト比を維持 */
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
}

このコードでは、auto-fitminmax()を組み合わせることで、画面幅に応じて列数が自動的に調整されます。メディアクエリを使わずにレスポンシブなグリッドを実現できる強力なテクニックです。

複雑なレイアウトの例

/* ダッシュボードレイアウト */
.dashboard {
  display: grid;
  gap: 16px;
  padding: 16px;
  /* モバイル: 1列 */
  grid-template-columns: 1fr;
  grid-template-areas:
    "header"
    "sidebar"
    "main"
    "footer";
}

/* タブレット: 2列 */
@media (min-width: 768px) {
  .dashboard {
    grid-template-columns: 250px 1fr;
    grid-template-areas:
      "header header"
      "sidebar main"
      "footer footer";
  }
}

/* デスクトップ: サイドバーの幅を広げる */
@media (min-width: 1024px) {
  .dashboard {
    grid-template-columns: 300px 1fr;
    max-width: 1400px;
    margin: 0 auto;
  }
}

.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }

画像のレスポンシブ対応

Next.jsのnext/imageコンポーネントを使用することで、レスポンシブな画像の提供と自動最適化が簡単に行えます。

fillプロパティを使った画像

fillプロパティを使うと、親要素のサイズに合わせて画像が自動調整されます。

import Image from 'next/image';
import styles from '../styles/image.module.css';

interface ResponsiveImageProps {
  src: string;
  alt: string;
}

export default function ResponsiveImage({ src, alt }: ResponsiveImageProps) {
  return (
    // 親要素にposition: relativeが必要
    <div className={styles.imageContainer}>
      <Image
        src={src}
        alt={alt}
        fill                           // 親要素いっぱいに広がる
        sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
        style={{ objectFit: 'cover' }} // 画像のフィット方法
        priority={false}               // LCPでない場合はfalse
      />
    </div>
  );
}
.imageContainer {
  position: relative;    /* fillを使う場合は必須 */
  width: 100%;
  /* アスペクト比を維持 */
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: 8px;
}

sizes属性の重要性

sizes属性は、ブラウザに画像の表示サイズを伝えるための重要な属性です。これにより、適切なサイズの画像が配信されます。

<Image
  src="/hero-image.jpg"
  alt="ヒーロー画像"
  fill
  sizes="
    (max-width: 640px) 100vw,
    (max-width: 1024px) 75vw,
    50vw
  "
/>

上記の設定の意味:

  • 画面幅640px以下: ビューポート幅の100%
  • 画面幅1024px以下: ビューポート幅の75%
  • それ以上: ビューポート幅の50%

固定サイズの画像

幅と高さが決まっている画像には、直接サイズを指定します。

import Image from 'next/image';

interface AvatarProps {
  src: string;
  name: string;
  size?: 'small' | 'medium' | 'large';
}

const sizeMap = {
  small: 32,
  medium: 48,
  large: 64,
};

export default function Avatar({ src, name, size = 'medium' }: AvatarProps) {
  const dimension = sizeMap[size];

  return (
    <Image
      src={src}
      alt={`${name}のアバター`}
      width={dimension}
      height={dimension}
      style={{ borderRadius: '50%' }}
    />
  );
}

Tailwind CSSを使った簡単なレスポンシブ設定

Tailwind CSSは、ユーティリティファーストのCSSフレームワークで、レスポンシブデザインを直感的に構築できます。

基本的なレスポンシブクラス

Tailwind CSSでは、以下のプレフィックスでブレイクポイントを指定します。

プレフィックス最小幅対象デバイス
(なし)0pxすべて(モバイル基準)
sm:640px大きめのスマートフォン
md:768pxタブレット
lg:1024px小さめのデスクトップ
xl:1280pxデスクトップ
2xl:1536px大画面
export default function ResponsiveText() {
  return (
    <div className="p-4 md:p-6 lg:p-8">
      {/* テキストサイズをレスポンシブに変更 */}
      <h1 className="text-2xl md:text-3xl lg:text-4xl font-bold">
        レスポンシブ見出し
      </h1>

      {/* 行間もデバイスに応じて調整 */}
      <p className="text-sm md:text-base lg:text-lg leading-relaxed md:leading-loose">
        このテキストは画面サイズに応じてサイズが変わります。
      </p>
    </div>
  );
}

レスポンシブなグリッドレイアウト

interface Product {
  id: number;
  name: string;
  price: number;
  image: string;
}

export default function ProductGrid({ products }: { products: Product[] }) {
  return (
    // モバイル:1列、sm:2列、md:3列、lg:4列
    <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 p-4">
      {products.map((product) => (
        <div
          key={product.id}
          className="bg-white rounded-lg shadow-md overflow-hidden
                     hover:shadow-lg transition-shadow duration-300"
        >
          <div className="relative aspect-square">
            <img
              src={product.image}
              alt={product.name}
              className="object-cover w-full h-full"
            />
          </div>
          <div className="p-4">
            <h3 className="font-semibold text-gray-800 truncate">
              {product.name}
            </h3>
            <p className="text-lg font-bold text-blue-600">
              ¥{product.price.toLocaleString()}
            </p>
          </div>
        </div>
      ))}
    </div>
  );
}

要素の表示/非表示の切り替え

export default function Navigation() {
  return (
    <nav className="bg-white shadow">
      <div className="max-w-7xl mx-auto px-4">
        <div className="flex justify-between items-center h-16">
          {/* ロゴ */}
          <div className="font-bold text-xl">MyApp</div>

          {/* モバイル: ハンバーガーメニュー表示 */}
          <button className="md:hidden p-2">
            <span className="sr-only">メニューを開く</span>
            {/* ハンバーガーアイコン */}
            <div className="w-6 h-0.5 bg-gray-600 mb-1.5" />
            <div className="w-6 h-0.5 bg-gray-600 mb-1.5" />
            <div className="w-6 h-0.5 bg-gray-600" />
          </button>

          {/* デスクトップ: ナビゲーションリンク表示 */}
          <div className="hidden md:flex space-x-8">
            <a href="/" className="text-gray-700 hover:text-blue-600">
              ホーム
            </a>
            <a href="/about" className="text-gray-700 hover:text-blue-600">
              会社概要
            </a>
            <a href="/products" className="text-gray-700 hover:text-blue-600">
              製品
            </a>
            <a href="/contact" className="text-gray-700 hover:text-blue-600">
              お問い合わせ
            </a>
          </div>
        </div>
      </div>
    </nav>
  );
}

レスポンシブデザインのベストプラクティス

1. コンテナクエリの活用

CSS Container Queriesを使うと、親要素のサイズに基づいてスタイルを変更できます。

.cardWrapper {
  container-type: inline-size;
  container-name: card;
}

.card {
  display: flex;
  flex-direction: column;
  padding: 16px;
}

/* 親要素が400px以上の場合 */
@container card (min-width: 400px) {
  .card {
    flex-direction: row;
    gap: 16px;
  }
}

2. clamp()関数による流動的なサイズ

.title {
  /* 最小24px、基本は4vw、最大48px */
  font-size: clamp(1.5rem, 4vw, 3rem);
}

.container {
  /* 最小300px、基本は90%、最大1200px */
  width: clamp(300px, 90%, 1200px);
  margin: 0 auto;
}

3. アスペクト比の維持

.videoWrapper {
  /* 16:9のアスペクト比を維持 */
  aspect-ratio: 16 / 9;
  width: 100%;
  background-color: #000;
}

.squareImage {
  /* 正方形を維持 */
  aspect-ratio: 1 / 1;
}

4. タッチフレンドリーなデザイン

モバイルデバイスでは、タップ可能な要素に適切なサイズを確保しましょう。

/* タップターゲットは最低44x44px */
.button {
  min-height: 44px;
  min-width: 44px;
  padding: 12px 24px;
}

/* リンク間の適切な間隔 */
.navLink {
  padding: 12px 16px;
  display: inline-block;
}

まとめ

Next.jsでのレスポンシブデザインは、以下の技術を組み合わせることで効率的に実装できます。

  • メディアクエリ: 画面サイズに応じたスタイルの切り替え
  • Flexbox/Grid: 柔軟で強力なレイアウト構築
  • next/image: 画像の自動最適化とレスポンシブ対応
  • Tailwind CSS: 直感的なユーティリティクラスによる迅速な開発

モバイルファーストのアプローチを採用し、ユーザーがどのデバイスからアクセスしても快適に利用できるWebアプリケーションを構築しましょう。

参考文献

円