ページの作成
親となるページを選択してください。
親ページに紐づくページを子ページといいます。
例: 親=スポーツ, 子1=サッカー, 子2=野球
子ページを親ページとして更に子ページを作成することも可能です。
例: 親=サッカー, 子=サッカーのルール
親ページはいつでも変更することが可能なのでとりあえず作ってみましょう!
この記事の要点
- サーバーコンポーネントは
async関数にできる。コンポーネントの中で直接awaitしてデータを取る。 - データベースへ直接問い合わせてよい。認証情報もクエリもブラウザには渡らない。
- バージョン 16 では
fetchは既定でキャッシュされない。取得が終わるまで描画がブロックされる点に注意する。 - 依存関係のない取得は
Promise.allで並列化する。awaitを並べるだけだと直列になる。 - 同じリクエスト内で同じ関数を何度も呼ぶなら
React.cacheで束ねる。props のバケツリレーを避けられる。
App Router の大きな利点のひとつが、データ取得のための専用 API を覚えなくてよいことです。サーバーコンポーネントは async 関数にできるため、必要なコンポーネントの中で直接 await すればよく、Pages Router 時代の getServerSideProps のような橋渡し役は不要になりました。この記事では、その基本形と、実務で効いてくる並列化・重複排除を扱います。
1もっとも基本の形
コンポーネントを async にして await するだけです。
// app/blog/page.tsx
export default async function Page() {
const res = await fetch('https://api.example.com/blog')
const posts = await res.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
この処理はサーバー上で走るため、レスポンスの JSON がそのままブラウザに流れることはありません。必要な部分だけを HTML にして返せます。
ここで押さえておきたい仕様が 2 つあります。
- 同じリクエスト内の同一
fetchは自動的にまとめられる(メモ化)。同じ URL・同じオプションであれば、複数のコンポーネントから呼んでも実際の通信は 1 回です。props で引き回さず、必要な場所で呼んでよい理由がこれです。 - バージョン 16 では
fetchの結果は既定でキャッシュされない。そして取得が終わるまでページの描画がブロックされます。バージョン 13 の頃は既定でキャッシュされていたため、古い記事と挙動が食い違います。
「ブロックされる」という性質への対処は 2 通りあります。結果を使い回してよいなら use cache でキャッシュする(キャッシュの仕組みと use cache)、毎回新鮮な値が要るなら Suspense で包んで先に画面を出す(ストリーミングで体感速度を上げる)。どちらを選ぶかがデータ設計の中心になります。
2データベースを直接触る
サーバーコンポーネントからは、ORM やデータベースクライアントを直接呼べます。接続情報もクエリもクライアントバンドルには含まれません。
// app/blog/page.tsx
import { db, posts } from '@/lib/db'
export default async function Page() {
const allPosts = await db.select().from(posts)
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
ただし「クライアントに漏れない」ことと「誰が見てよいか」は別の話です。ログインしているユーザーが本当にそのデータを見てよいのかという認可の確認は、必ずサーバー側の取得処理の中で行ってください。URL を直接叩かれる可能性は常にあります。
3直列と並列
実装で最も差が出るのがここです。await を素直に並べると、前の通信が終わるまで次が始まりません。
// これは直列になる(遅い)
const artist = await getArtist(username)
const albums = await getAlbums(username)
依存関係がないなら、先に呼び出しだけ行って後からまとめて待ちます。fetch は呼んだ時点で通信が始まるため、これで並列になります。
// 呼び出しを先に始めてから、まとめて待つ(速い)
const artistData = getArtist(username)
const albumsData = getAlbums(username)
const [artist, albums] = await Promise.all([artistData, albumsData])
なお Promise.all は 1 つでも失敗すると全体が失敗します。一部が落ちても残りを表示したい場合は Promise.allSettled を使います。
後続が前の結果に依存する「本当に直列でしか書けない」ケースもあります。その場合は、依存する部分だけを子コンポーネントに切り出して Suspense で包むと、先に取れたデータから順に画面へ出せます。
export default async function Page({ params }) {
const { username } = await params
const artist = await getArtist(username)
return (
<>
<h1>{artist.name}</h1>
<Suspense fallback={<div>読み込み中…</div>}>
<Playlists artistID={artist.id} />
</Suspense>
</>
)
}
4同じ取得を束ねる React.cache
fetch は自動でメモ化されますが、データベース問い合わせや自作の関数はそうではありません。同じリクエストの中で複数箇所から呼ぶ場合は React.cache で包みます。
// app/lib/user.ts
import { cache } from 'react'
export const getUser = cache(async () => {
const res = await fetch('https://api.example.com/user')
return res.json()
})
これで、レイアウトとページの両方から getUser() を呼んでも実行は 1 回です。メタデータ生成(generateMetadata)とページ本体で同じデータが要るときにも定番の手法になります。
注意点として、React.cache の効果はそのリクエストの中だけです。リクエストをまたいで結果を使い回したい場合は、キャッシュの仕組みのほうを使います。
5クライアント側で取得する場合
ブラウザ側でデータを取る必要がある場面もあります。選択肢は主に 2 つです。
| 手段 | 特徴 | 向いている場面 |
|---|---|---|
React の use API | サーバーで開始した Promise をそのまま props で渡し、クライアントで解決する | 初回表示をストリーミングしたい |
| SWR / TanStack Query | 再取得・キャッシュ・失効管理を持つ | 操作に応じて何度も取り直す画面 |
use を使う場合、サーバー側では Promise を await せずに渡すのがポイントです。
// app/blog/page.tsx(サーバー)
export default function Page() {
const posts = getPosts() // await しない
return (
<Suspense fallback={<div>読み込み中…</div>}>
<Posts posts={posts} />
</Suspense>
)
}
// app/ui/posts.tsx(クライアント)
'use client'
import { use } from 'react'
export default function Posts({ posts }) {
const allPosts = use(posts)
return <ul>{allPosts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>
}
6つまずきやすいところ
- 全体が遅い:どこかで直列になっていないか確認します。レイアウトとページはもともと並列に描画されますが、1 つのコンポーネント内の
awaitの並びは直列です。 - キャッシュされていると思い込む:バージョン 16 の
fetchは既定でキャッシュしません。「一度取ったから速いはず」という前提でコードを書くと、負荷試験で崩れます。 - props のバケツリレーが深い:
fetchのメモ化やReact.cacheを使えば、必要なコンポーネントで直接呼ぶ形に整理できます。 - クライアントから直接 DB を触ろうとする:できません。取得はサーバー、更新は Server Functions か Route Handlers を経由します。
取得の書き方が分かったら、次はその待ち時間をどう見せるかです。
ページの作成
親となるページを選択してください。
親ページに紐づくページを子ページといいます。
例: 親=スポーツ, 子1=サッカー, 子2=野球
子ページを親ページとして更に子ページを作成することも可能です。
例: 親=サッカー, 子=サッカーのルール
親ページはいつでも変更することが可能なのでとりあえず作ってみましょう!
子ページはありません
人気ページ
- 1 Eclipseで「サーバーに追加または除去できるリソースがありません。」の原因と対処法
- 2 tomcat の起動 / 停止ログと catalina.log・catalina.out の違い
- 3 JavaScript で base URL を取得する方法|window.location.origin
- 4 YouTube Data API v3 エラー一覧|403・400・404 の原因と対処
- 5 Laravel エラー一覧|500/Blade/DB 接続/ルーティングの代表エラー
- 6 Spring Frameworkのアノテーション一覧
- 7 3Dグラフィックスとは|モデリング/レンダリング/主要ソフトウェア (Blender / Maya)
- 8 【Spring】@Valueアノテーションとは
- 9 CATALINA_HOME の確認方法 (Linux / Mac)
- 10 【Spring】@Autowiredアノテーションとは
最近更新/作成されたページ
- PHPでのファイルアップロード方法|multipart/form-dataと$_FILES 2026-08-07 17:15:57
- PHP $_SERVERとは|DOCUMENT_ROOT・PHP_SELFなど主な要素 2026-08-07 17:15:57
- Scratchのアカウントの作り方|メールアドレスとユーザー名の登録手順 2026-08-07 17:15:57
- djangoにおけるMVCアプリケーション実装例 2026-08-07 17:15:57
- Bluetoothとは|仕組み・BLEとClassicの違い・用途 2026-08-07 17:15:57
- Spring Bootのテスト入門|@SpringBootTestとJUnitでの単体・結合テスト 2026-08-07 17:15:57
- Java とは?言語仕様・JVM・主要フレームワーク一覧 2026-08-07 17:15:56
- インストール方法(Windows版) 2026-08-07 17:15:56
- APIキー取得方法 2026-08-07 17:15:56
- 【PHPフレームワーク】Laravelの使い方 2026-08-07 17:15:56
- Scratch 作品の公開方法 2026-08-07 17:15:56
- Google OAuth 2.0 認証の実装方法 2026-08-07 17:15:56
- データベースとは?RDB / NoSQL / SQL の基礎 2026-08-07 17:15:56
- インストール(eclipseプラグイン) 2026-08-07 17:15:56
- Spring Frameworkの使い方 2026-08-07 17:15:56
コメントを削除してもよろしいでしょうか?