© TOOLS BOX — Next.js / React / TypeScript コードサンプル集

サンプルガイド
←サンプル一覧
nextjsui-component

Next.js App Router の loading.tsx と Suspense でローディング UI を実装する

loading.tsx を配置するだけでルートセグメント単位のローディング UI が自動適用される仕組みと、Suspense を使った部分的な待機表示の実装例。

難易度: 初級·更新: 2026-04-17

対応バージョン

nextjs 15react 19

前提環境

Next.js App Router のディレクトリ構成(layout.tsx / page.tsx)を理解していること

概要

Next.js App Router では、ルートセグメントに loading.tsx を置くだけでページ全体のローディング UI が自動適用される。 内部的には page.tsx が <Suspense> でラップされるため、サーバーコンポーネントの非同期処理が完了するまで loading.tsx の内容が表示される。 コンポーネント単位で待機させたい場合は <Suspense fallback={...}> を直接使う。

インストール

# 追加インストールは不要

loading.tsx によるページ単位のローディング UI

// src/app/posts/loading.tsx
export default function Loading() {
  return (
    <div className="space-y-4">
      {Array.from({ length: 5 }).map((_, i) => (
        <div key={i} className="animate-pulse rounded border p-4">
          <div className="mb-2 h-4 w-1/3 rounded bg-gray-200" />
          <div className="h-3 w-full rounded bg-gray-100" />
        </div>
      ))}
    </div>
  );
}
// src/app/posts/page.tsx
async function getPosts() {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts");
  if (!res.ok) throw new Error("取得に失敗しました");
  return res.json() as Promise<{ id: number; title: string; body: string }[]>;
}

export default async function PostsPage() {
  const posts = await getPosts();

  return (
    <ul className="space-y-4">
      {posts.map((post) => (
        <li key={post.id} className="rounded border p-4 text-sm">
          <p className="font-medium">{post.title}</p>
          <p className="text-gray-500">{post.body}</p>
        </li>
      ))}
    </ul>
  );
}

Suspense によるコンポーネント単位のローディング UI

ページの一部だけを遅延させたい場合は <Suspense> を直接使う。

// src/app/dashboard/page.tsx
import { Suspense } from "react";

function SkeletonCard() {
  return (
    <div className="animate-pulse rounded border p-4">
      <div className="mb-2 h-4 w-1/2 rounded bg-gray-200" />
      <div className="h-3 w-full rounded bg-gray-100" />
    </div>
  );
}

async function RecentPosts() {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=3");
  const posts = (await res.json()) as { id: number; title: string }[];

  return (
    <ul className="space-y-2">
      {posts.map((post) => (
        <li key={post.id} className="rounded border px-4 py-2 text-sm">
          {post.title}
        </li>
      ))}
    </ul>
  );
}

export default function DashboardPage() {
  return (
    <div className="space-y-6">
      <h1 className="text-lg font-bold">ダッシュボード</h1>
      <section>
        <h2 className="mb-2 text-sm font-medium text-gray-600">最近の投稿</h2>
        <Suspense fallback={<SkeletonCard />}>
          <RecentPosts />
        </Suspense>
      </section>
    </div>
  );
}

ポイント

  • loading.tsx を置くだけでルートセグメント全体にローディング UI が適用される(page.tsx が自動的に <Suspense> でラップされる)
  • <Suspense fallback={...}> はコンポーネント単位で使え、ページの一部だけを遅延させられる
  • スケルトン UI は animate-pulse と背景色だけで実装できる(外部ライブラリ不要)
  • loading.tsx は同一セグメントの layout.tsx の中で機能するため、ヘッダーやナビゲーションはローディング中も表示され続ける

注意点

loading.tsx はそのセグメント配下の page.tsx を自動的に Suspense でラップする。コンポーネント単位で待機させたい場合は Suspense を直接使う。どちらも fallback に同じスケルトンコンポーネントを渡せる。

関連サンプル

同じテーマや技術スタックを使った実装例

  • Next.js Intercepting Routes で URL 付きモーダルを実装する

    Intercepting Routes((..) 記法)と Parallel Routes を組み合わせ、リスト画面の URL を維持したままモーダルで詳細を表示する実装例。

  • Next.js の Parallel Routes で複数スロットを同時表示する

    @スロット記法を使い、同一ページに複数の独立したルートコンテンツを並列表示する実装例。

  • Tailwind CSS でローディング中のスケルトンカード UI を実装する

    コンテンツ読み込み中に表示するスケルトン(骨格)カード UI を Tailwind CSS の animate-pulse で実装し、一覧グリッドに並べるパターン。

  • React で関連サンプル一覧を表示して回遊導線を作る

    slug の配列から関連サンプルのメタデータを取得し、カード形式で一覧表示する回遊導線コンポーネントの実装例。

  • Next.js で not-found.tsx を使ってカスタム 404 ページを作る

    notFound() 関数と not-found.tsx を組み合わせてカスタム 404 ページを実装する例。動的ルートでリソースが存在しない場合の処理パターンを示す。

関連仕様

このサンプルを理解するのに役立つ仕様や概念

  • FrameworkNext.jsReact ベースのフルスタックフレームワーク。SSR・SSG・App Router・API Routes を提供する。
←サンプル一覧に戻る