Урок 01

Загрузка данных в Server Components

Server Components выполняются на сервере и могут напрямую обращаться к данным.

Async-компоненты

// app/posts/page.tsx
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 ждёт промис.

Доступ к БД напрямую

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-роут для внутренней загрузки. Меньше кода, быстрее.

Переменные окружения

На сервере доступны все переменные:

const apiKey = process.env.API_KEY; // работает
const dbUrl = process.env.DATABASE_URL;

На клиенте — только NEXT_PUBLIC_*.

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

Плохо — последовательно:

const user = await getUser();
const posts = await getPosts();
const comments = await getComments();

Время = user + posts + comments.

Хорошо — параллельно:

const [user, posts, comments] = await Promise.all([
    getUser(),
    getPosts(),
    getComments(),
]);

Время = max(user, posts, comments).

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

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

    if (!post) notFound();

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

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>;
}

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

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

    try {
        const post = await getPost(id);
        if (!post) notFound();
        return <h1>{post.title}</h1>;
    } catch (error) {
        throw error; // поймает error.tsx
    }
}

Suspense для медленных частей

import { Suspense } from 'react';

export default function Page() {
    return (
        <>
            <Header />
            <Suspense fallback={<PostsSkeleton />}>
                <Posts />
            </Suspense>
            <Suspense fallback={<CommentsSkeleton />}>
                <Comments />
            </Suspense>
        </>
    );
}

Каждый блок грузится независимо. Страница не ждёт самого медленного.

Streaming

Next.js стримит HTML по мере готовности. Пользователь видит части страницы, пока грузятся остальные.

Когда использовать API-роуты

  • Webhooks от внешних сервисов
  • Публичные API для мобильных приложений
  • Внешние интеграции

Для внутренней загрузки — прямое обращение к БД или fetch.

Итоги

  • Server Components — async/await
  • Прямой доступ к БД
  • Promise.all для параллельности
  • params и searchParams — промисы
  • Suspense для стриминга
  • API-роуты для внешних клиентов