Server и Client Components
В App Router компоненты по умолчанию — серверные. Это ключевое отличие Next.js от обычного React.
Server Components (по умолчанию)
Выполняются только на сервере. В браузер отправляется готовый HTML, а не JS компонента.
// 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>
);
}
Плюсы:
- Прямой доступ к БД, файлам, переменным окружения
- Меньше JS в браузере
- Быстрее первая загрузка
- Можно
async/awaitпрямо в компоненте
Ограничения:
- Нет
useState,useEffect - Нет обработчиков событий (
onClick) - Нет браузерных API (
window,localStorage)
Client Components
Выполняются в браузере. Помечаются директивой 'use client':
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
Когда нужны:
- Хуки (
useState,useEffect,useRef) - Обработчики событий
- Браузерные API
- Контекст (
useContext)
Разделение
Не весь компонент должен быть клиентским. Выноси интерактивную часть:
// app/page.tsx — серверный
import LikeButton from './LikeButton';
export default async function Post() {
const post = await getPost();
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
<LikeButton postId={post.id} />
</article>
);
}
// app/LikeButton.tsx — клиентский
'use client';
export default function LikeButton({ postId }: { postId: number }) {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!liked)}>{liked ? '❤️' : '🤍'}</button>;
}
Серверный рендерит контент, клиентский — интерактив.
Передача данных
Серверный → клиентский через пропсы:
// Серверный
<LikeButton postId={post.id} initialLiked={post.liked} />
Пропсы должны быть сериализуемыми — без функций, классов, символов.
Клиентский внутри серверного
Можно вкладывать. Наоборот — нет (нельзя импортировать серверный в клиентский).
// Серверный
import ClientComponent from './Client';
export default function Page() {
return (
<div>
<h1>Серверная часть</h1>
<ClientComponent />
</div>
);
}
children в клиентском
Можно передавать серверный контент как children:
// ClientWrapper.tsx
'use client';
export default function ClientWrapper({ children }: { children: React.ReactNode }) {
const [open, setOpen] = useState(false);
return (
<div>
<button onClick={() => setOpen(!open)}>Toggle</button>
{open && children}
</div>
);
}
// page.tsx — серверный
<ClientWrapper>
<ServerContent />
</ClientWrapper>
Серверный контент рендерится на сервере, клиентский — управляет.
Правила
- По умолчанию — серверные
'use client'— только когда нужны хуки или события'use client'влияет на весь файл и его импорты- Не импортируй серверный в клиентский
- Пропсы серверный → клиентский — сериализуемые
Итоги
- Server Components — по умолчанию
- Меньше JS, прямой доступ к данным
- Client Components — для интерактива
'use client'наверху файла- Разделяй: контент на сервере, интерактив на клиенте