Получение данных и стриминг
Данные прямо в Server Components, параллельно, Suspense и params как Promise.
Обновлено
Что значит получение данных в Next
Получение данных (data fetching) = загрузка данных (из БД, из API), которые нужны странице. В Next App Router место по умолчанию — Server Component: async-функция, которая читает данные напрямую и рендерит результат.
export default async function Page() {
const posts = await db.post.findMany() // ORM, SQL, fetch — что угодно
return <PostList posts={posts} />
}По сравнению с классическим React (useEffect + fetch + useState): нет состояния загрузки на клиенте, нет промежуточного API-маршрута, нет раскрытых секретов, нет водопада клиент → сервер → БД.
Способы получать данные — и когда
| Способ | Где | Когда |
|---|---|---|
прямой await в Server Component |
сервер | по умолчанию — любой вывод данных |
Promise, переданный в Client Component + use() |
сервер запускает, клиент читает | клиентскому компоненту нужны данные, но хочется стриминга |
| TanStack Query | клиент | очень интерактивные данные (polling, бесконечная прокрутка) — Серверное состояние |
| Route Handler | сервер | API для других / вебхуки — не для своих страниц |
useEffect + fetch |
клиент | почти никогда |
Параллельно, а не каскадом
Водопад (waterfall) = запросы, которые без причины ждут друг друга.
// ✗ водопад — 2 × время
const user = await getUser(id)
const posts = await getPosts(id)
// ✓ параллельно
const [user, posts] = await Promise.all([getUser(id), getPosts(id)])Что такое стриминг
Без стриминга пользователь видит страницу, только когда готовы все данные. Со стримингом сервер отправляет HTML частями: готовое показывается сразу, медленные части приходят, когда закончат.
Инструмент — <Suspense>.
export default function Page() {
return (
<>
<Header /> {/* появляется сразу */}
<Suspense fallback={<Skeleton />}>
<Recommendations /> {/* async, появляется, когда готов */}
</Suspense>
</>
)
}loading.tsx = автоматический <Suspense> вокруг всей страницы.
Promise, переданный клиенту
Запрос запускается на сервере, а читается в Client Component:
// сервер
const statsPromise = getStats() // без await — не блокирует
<Suspense fallback={<Spinner />}>
<Stats data={statsPromise} />
</Suspense>
// клиент
'use client'
import { use } from 'react'
export function Stats({ data }: { data: Promise<Stats> }) {
const stats = use(data) // приостанавливается до получения
}Данные времени выполнения
cookies(), headers(), searchParams, params (для значений, не сгенерированных заранее) зависят от запроса — их нельзя узнать при сборке. Компоненты, которые их читают, становятся динамическими и должны быть внутри <Suspense>, чтобы остальная страница осталась статичной. Именно так в этом приложении показывается ваш прогресс.
Дедупликация
Одна и та же функция вызывается из layout + page + generateMetadata?
| Инструмент | Время жизни |
|---|---|
cache() из React |
в пределах одного запроса |
'use cache' |
постоянно, между запросами — урок Кэш |
Коротко
- Данные → прямо в Server Components через
await. Promise.allдля независимых запросов.<Suspense>для стриминга; данные времени выполнения — внутри Suspense.