Пошук уроків, статей та іншого контенту
Навчимося виконувати запити через fetch у Server Components і передавати отримані дані в інтерфейс.
У Next.js компоненти, розташовані в app, за замовчуванням є Server Components. Вони виконуються на сервері, тому в них можна:
виконувати запити до API;
працювати з серверними ресурсами;
не передавати код запиту в браузер;
отримувати дані до формування інтерфейсу.
Server Component може бути асинхронною функцією. Це дозволяє використовувати await fetch(...) безпосередньо всередині компонента.
fetchСтворимо сторінку, яка отримує список дописів із зовнішнього API.
Файл app/page.tsx:
type Post = {
id: number;
title: string;
body: string;
};
async function getPosts(): Promise<Post[]> {
const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
cache: "no-store",
});
if (!response.ok) {
throw new Error("Не вдалося завантажити дописи");
}
return response.json();
}
export default async function HomePage() {
const posts = await getPosts();
return (
<main>
<h1>Останні дописи</h1>
<ul>
{posts.slice(0, 5).map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</li>
))}
</ul>
</main>
);
}Розглянемо послідовність виконання:
Next.js викликає HomePage на сервері.
Функція getPosts виконує HTTP-запит через fetch.
Відповідь перетворюється на JavaScript-масив за допомогою response.json().
Отриманий масив зберігається у змінній posts.
Дані використовуються під час формування JSX.
Користувач отримує готовий інтерфейс із дописами.
Метод fetch не вважає HTTP-помилки винятками автоматично. Наприклад, відповідь зі статусом 404 або 500 все одно буде повернута як об’єкт Response.
Тому варто перевіряти response.ok:
if (!response.ok) {
throw new Error("Помилка під час запиту");
}Властивість response.ok має значення true, якщо статус відповіді належить до успішних HTTP-статусів.
Після перевірки можна безпечно отримати тіло відповіді:
const data = await response.json();Отримані дані не обов’язково відображати в тому самому компоненті. Їх можна передати дочірньому компоненту через props.
Файл app/components/PostList.tsx:
type Post = {
id: number;
title: string;
body: string;
};
type PostListProps = {
posts: Post[];
};
export default function PostList({ posts }: PostListProps) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</li>
))}
</ul>
);
}Файл app/page.tsx:
import PostList from "./components/PostList";
type Post = {
id: number;
title: string;
body: string;
};
async function getPosts(): Promise<Post[]> {
const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
cache: "no-store",
});
if (!response.ok) {
throw new Error("Не вдалося завантажити дописи");
}
return response.json();
}
export default async function HomePage() {
const posts = await getPosts();
return (
<main>
<h1>Останні дописи</h1>
<PostList posts={posts.slice(0, 5)} />
</main>
);
}У цьому прикладі:
HomePage отримує дані на сервері;
posts передається в PostList;
PostList відповідає лише за відображення списку;
браузеру не потрібно самостійно виконувати цей запит.
У fetch можна вказати параметр cache.
const response = await fetch("https://example.com/api/posts", {
cache: "no-store",
});cache: "no-store" означає, що дані потрібно отримувати під час кожного запиту до сторінки. Це зручно для даних, які часто змінюються.
const response = await fetch("https://example.com/api/posts", {
cache: "force-cache",
});cache: "force-cache" дозволяє використовувати кешовану відповідь. Такий варіант може бути корисним для даних, які змінюються рідко.
Для навчального прикладу cache: "no-store" допомагає щоразу бачити актуальний результат запиту.
Якщо проєкт використовує TypeScript, бажано описати структуру даних:
type Post = {
id: number;
title: string;
body: string;
};Повертаючий тип функції також можна вказати явно:
async function getPosts(): Promise<Post[]> {
// ...
}Це допомагає редактору підказувати властивості об’єкта та виявляти помилки під час розробки.
Важливо пам’ятати, що TypeScript не перевіряє автоматично фактичну структуру даних, отриманих із сервера. Тип Post описує очікуваний формат, але API все одно має повертати дані саме такого вигляду.
useEffectУ Client Components запити часто виконують через useEffect. Для Server Components це не потрібно:
export default async function HomePage() {
const response = await fetch("https://example.com/api/posts");
const posts = await response.json();
return <div>{/* відображення posts */}</div>;
}Тут запит виконується під час роботи компонента на сервері. Не потрібно додавати:
"use client";і не потрібно використовувати useEffect лише для завантаження початкових даних.
use client без потреби"use client";
export default async function HomePage() {
// ...
}Client Components не можна використовувати як звичайні асинхронні Server Components. Якщо запит потрібен лише для отримання даних під час рендерингу, залишайте компонент Server Component і не додавайте "use client".
awaitНеправильно:
const response = fetch("https://example.com/api/posts");
const posts = response.json();fetch і response.json() повертають проміси. Правильний варіант:
const response = await fetch("https://example.com/api/posts");
const posts = await response.json();response.okНеправильно вважати, що будь-яка відповідь fetch є успішною:
const response = await fetch("https://example.com/api/posts");
const posts = await response.json();Краще перевірити статус відповіді:
if (!response.ok) {
throw new Error("Помилка запиту");
}key під час виведення спискуНеправильно:
{posts.map((post) => (
<li>{post.title}</li>
))}Правильно:
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}key допомагає React правильно розрізняти елементи списку.
Запити до приватних API та роботу з секретними ключами краще виконувати в Server Components. Не передавайте секрети в компонент із "use client", адже його код може потрапити до браузера.
Server Components у Next.js за замовчуванням виконуються на сервері.
У них можна використовувати асинхронні функції та await fetch(...).
Після запиту потрібно перевіряти response.ok.
Дані з відповіді отримують через await response.json().
Отримані дані можна безпосередньо відобразити або передати дочірньому компоненту через props.
Параметр cache визначає, як Next.js працює з відповіддю запиту.
Для запитів у Server Components зазвичай не потрібні "use client" і useEffect.