データの取得に時間がかかるページで、画面が真っ白なまま止まってしまうと、ユーザーは「壊れたのかな?」と不安になります。こうしたときに「読み込み中です」と伝えるのがローディングUIです。Next.js の App Router では、loading.tsx というファイルを置くだけで、この読み込み中の表示を自動で出せます。この記事では、loading.tsx の基本の使い方、どういう仕組みで動いているのか(React の Suspense との関係)、そしてフォルダ単位で表示範囲を分ける方法までを、初心者向けに解説します。
目次
loading.tsx はファイルを置くだけで有効になる
App Router では、page.tsx と同じフォルダに loading.tsx という名前でファイルを作ると、そのページの読み込み中に自動で表示されるようになります。特別な設定は要りません。ファイルの命名がそのまま機能につながる、Next.js らしい仕組みです。まずは最小の例を見てみましょう。
// このフォルダのページが読み込まれる間、自動で表示される
export default function Loading() {
return <p>読み込み中です...</p>;
}
これと同じフォルダに、時間のかかるデータ取得を行うページを置きます。App Router の Server Component では、async にした関数の中で await を使ってデータを取得できます。
// データ取得に時間がかかるページ
export default async function DashboardPage() {
// 取得が終わるまでの間、loading.tsx が表示される
const res = await fetch("https://example.com/api/data");
const data = await res.json();
return <div>{data.title}</div>;
}
このページを開くと、await しているデータの取得が終わるまでは loading.tsx の内容(「読み込み中です…」)が表示され、取得が完了すると自動的にページ本体に切り替わります。開発者は「いつ切り替えるか」を一切書く必要がありません。
裏では Suspense が使われている
この便利な仕組みの正体は、React の Suspense です。Suspense は「まだ準備できていないコンポーネントの代わりに、一時的な表示(フォールバック)を出す」ための機能です。Next.js は、loading.tsx を置くと、内部的にページを Suspense で包み、そのフォールバックとして loading.tsx を使ってくれます。
つまり、次のようなコードを Next.js が自動で組み立ててくれている、とイメージすると分かりやすいです。
// loading.tsx を置くと、Next.js が内部でこう包んでくれる
<Suspense fallback={<Loading />}>
<DashboardPage />
</Suspense>
この仕組みのおかげで、ローディング表示中でもヘッダーやナビゲーションといった共通部分(layout.tsx)はそのまま表示され続けます。読み込み中なのはページの中身だけで、画面全体が真っ白になることはありません。ユーザーはすぐに操作できる部分を目にできるため、体感の速さが向上します。
スケルトンを表示して体感を良くする
loading.tsx の中身は普通のコンポーネントなので、自由にデザインできます。単なる「読み込み中」の文字よりも、実際のレイアウトに近いスケルトン(中身が入る前の骨組み表示)を出すと、完成後の画面が予想でき、より自然な待ち時間になります。
// 実際のレイアウトに近い骨組み(スケルトン)を表示する例
export default function Loading() {
return (
<div className="skeleton">
<div className="skeleton-title" />
<div className="skeleton-line" />
<div className="skeleton-line" />
</div>
);
}
スケルトンの見た目は CSS で作ります(グレーの四角に薄いアニメーションを付けるのが定番です)。ポイントは、実際のページと近い形・位置にしておくこと。読み込みが終わったときのガタつきが減り、洗練された印象になります。
フォルダ単位で表示を分けられる
loading.tsx は、置いたフォルダ(ルートセグメント)とその配下にだけ効きます。そのため、ページごとに違うローディング表示を用意できます。たとえば次のような構成なら、それぞれのフォルダに別々の loading.tsx を置けます。
app/
├─ dashboard/
│ ├─ loading.tsx ← dashboard 用のローディング
│ └─ page.tsx
└─ posts/
├─ loading.tsx ← posts 用のローディング
└─ page.tsx
/dashboard を開いたときは dashboard/loading.tsx が、/posts を開いたときは posts/loading.tsx が使われます。ページの性質に合わせて、リスト用のスケルトン、詳細ページ用のスケルトン、といった具合に細かく作り分けられるのが便利なところです。共通のローディングでよければ、上位のフォルダに1つ置いておけば配下すべてに適用されます。
ローディングが表示されないとき
loading.tsx を置いたのに表示されない、というときは、ページ側が実際に待っているかを確認しましょう。ローディングUIは「ページの描画がデータ取得などで中断されている間」に出るものです。ページが async になっておらず、await で何も待っていない場合、待ち時間がないため一瞬で描画が終わり、ローディングは見えません。時間のかかる処理を await しているページで初めて意味を持ちます。ローカルで動きを確認したいときは、取得先のレスポンスが速すぎて一瞬で終わることもあるので、通信の遅い環境で試すと分かりやすいです。
まとめ
Next.js App Router の loading.tsx は、page.tsx と同じフォルダに置くだけで、ページの読み込み中に自動でローディングUIを表示してくれる仕組みです。裏側では React の Suspense が使われており、ページ本体を包んで、準備できるまでの間フォールバックとして loading.tsx を表示します。ヘッダーなどの共通部分は表示したまま、中身だけを読み込み中にできるため、画面が真っ白になりません。中身は自由に作れるので、スケルトンで完成形を予感させると体感がさらに良くなります。フォルダ単位で使い分けられる点も含め、ユーザー体験を手軽に底上げできる機能です。