Урок 05

Загрузка данных

В Server Components можно делать fetch напрямую и использовать async/await.

Базовый fetch

export default async function Posts() {
    const res = await fetch('https://api.example.com/posts');
    const posts = await res.json();

    return <ul>{posts.map((p) => <li key={p.id}>{p.title}</li>)}</ul>;
}

Компонент — async. Next.js ждёт промис и рендерит результат.

Параллельная загрузка

Не жди по очереди:

// Плохо — последовательно
const user = await getUser();
const posts = await getPosts();
const comments = await getComments();

// Хорошо — параллельно
const [user, posts, comments] = await Promise.all([
    getUser(),
    getPosts(),
    getComments(),
]);

Динамические параметры

export default async function Post({ params }: { params: Promise<{ id: string }> }) {
    const { id } = await params;
    const post = await getPost(id);

    return <h1>{post.title}</h1>;
}

params — промис, нужно await.

Search params

export default async function Search({
    searchParams,
}: {
    searchParams: Promise<{ q?: string }>;
}) {
    const { q } = await searchParams;
    const results = await search(q);

    return <ul>{results.map((r) => <li key={r.id}>{r.title}</li>)}</ul>;
}

Доступ к БД

Server Components могут работать с БД напрямую:

import { db } from '@/lib/db';

export default async function Users() {
    const users = await db.user.findMany();
    return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
}

Не нужен API-роут для внутренней загрузки.

Кеширование

По умолчанию fetch кешируется в Next.js:

const res = await fetch('https://api.example.com/data'); // кеш

Отключить:

const res = await fetch('https://api.example.com/data', {
    cache: 'no-store',
});

Revalidate через время:

const res = await fetch('https://api.example.com/data', {
    next: { revalidate: 3600 }, // 1 час
});

С тегом:

const res = await fetch('https://api.example.com/posts', {
    next: { tags: ['posts'] },
});

Перегенерация:

import { revalidateTag } from 'next/cache';

revalidateTag('posts');

Suspense

Оберни медленную часть в <Suspense>:

import { Suspense } from 'react';

export default function Page() {
    return (
        <>
            <h1>Заголовок</h1>
            <Suspense fallback={<Skeleton />}>
                <SlowComponent />
            </Suspense>
        </>
    );
}

async function SlowComponent() {
    const data = await fetchSlowData();
    return <div>{data}</div>;
}

Страница показывается сразу, SlowComponent — по мере готовности.

loading.tsx

Автоматический Suspense для страницы:

// app/posts/loading.tsx
export default function Loading() {
    return <Skeleton />;
}

Показывается, пока страница грузится.

Обработка ошибок

export default async function Post({ params }) {
    const { id } = await params;
    const post = await getPost(id);

    if (!post) notFound();

    return <h1>{post.title}</h1>;
}

Или через error.tsx в папке.

Итоги

  • Server Components — async/await напрямую
  • Параллельно через Promise.all
  • params и searchParams — промисы
  • Кеш fetch по умолчанию
  • revalidate и теги
  • Suspense для стриминга