Дзе павінна быть лінія сервер-кліент у сторанцы Next.js App Router
Практычны модель разуму для React Server Components: што запускаецца дзе, як сторніца пасты ў блогу дзеліцца на часткі сервера і кліента, а таксама правілы імпорту, якія трэба дапэўнаваць.
Компаненты сервера у React частаца здаюцца складнымі нават пасля прачытання дакументацыі, галоўная прычына — яны вымагаюць адмовысяць ад прыпуску, які кожны разработчык React выконваў гады: што компаненты работаюць у браузеры. Калі гэты прыпуск зникае, решта выклікаецца досыць прыродна. У гэтым артыкуле ствараецца ментальная модель на прыкладзе адной сторонічкі блогу, паказваюцца, якія частынкі належаць на серверы, якія — на кліент, а таксама колькае правіл, якія падтрымваюць чыстую межу між ямі.
Ёсць глыбокейшая інформацыя пра сам процес атрыбутавання, можна пазначыцца на архітектуру атрыбутавання без выкарыстоўвання бандла за дапамогою компанентав сервера.
З „всё атрыбутуецца“ да „толькі тое, што трэба“
Даўнае раней Компанентав сервера кожны компанент React выконваліся ў браузеры. Нават з рэндарованнем са стороны сервера, весь JavaScript для всіх компанентав быў адправлены на кліента і запрацоўваў, каб аплікацыя магла стаць інтэрактывной. Гэта марнатратна для компанентав, якія толькі запрашаюць даны і перадаюцы іх як пропсы: их код завантажваецца і выконваліся, але ніколі не рэагуе на корыстніка.
Компанент сервера зменяе гэта, выконвалісячы толькі на серверы. Його код ніколі не адправляецца у браузер і не запрацоўваліся, таму ён не дадае JavaScript у пакет кліента. У браузеры доходзіць толькі його рэндараваны выхід, які React серыялізуе ў компактны пакет разам з HTML.
Спадчайшы спосаб прыметы два гэтыя типы:
- Кліентскі компанент: выконвалісяў у браузеры (пасля прадзейсвовання на серверы), можа зберагчы статус і рэагаваць на запускі.
Чаму такая разліка мае значэнне
Возьмем типовую стороніцу пастатку на блог. Загалоўкі і тэксты беруцца з базы дадзеных, выглядаюць однакова для кожнаго візітара і не адчуваюць клікаў. Традыцыйна код, які ўзорамляе іх, а таксама будь-якія бібліятэкі markdown або форматавання, яшчэ такі ж праходзіць да браузера. За дапамогою компанент сервера ўсё гэта застаецца на серверы. Размер пакета зменшваецца, а стороніцы часта завантажуюцца быстрэй, ўсабліва на медленнейшых прыстроях.
Команды, якія пераходзяць на App Router, створаны ўакол компанент сервера, апісалі кращыя показнікі Core Web Vitals, у прыватнасці LCP, у дзеярых сцэнарыях. Спраўляйцеся з гэтым як з чыннікаў, залежнымі ад контексту: выгода залежыць ад таго, сколькі кліентскага JavaScript вашы стороніцы пасвячуюць, таму пераканайцеся самі ў своіх маршрутах.
Стороніца пастатку на блог, раздзелена на два часткі
У проекте Next.js App Router файл сторанкі ёсць серверным компонентам, якщо толькі не зазначана іншая справа. Наведзена нижэй сторанка ёсць async функцыяю, яка чакае данні безпасова з слоя дадзейнаў, карыцца ситуацыяй, калі элемент не знайдзены, атрыбуе статычны кантэнт, а пасля дадае адна інтерактыўная дзецячая елемент для каментаў. Яна напісана на адпаведнасць API Next.js 14:
// app/posts/[slug]/page.tsx
// This is a Server Component by default — no "use client" needed
import { getPostBySlug } from "@/lib/db"
import { PostContent } from "@/components/PostContent"
import { CommentSection } from "@/components/CommentSection"
type Props = {
params: { slug: string }
}
export default async function PostPage({ params }: Props) {
// Direct DB call — no useEffect, no API route, no loading state
const post = await getPostBySlug(params.slug)
if (!post) {
return <div>Post not found.</div>
}
return (
<article className="max-w-2xl mx-auto py-12 px-4">
<h1 className="text-3xl font-bold mb-4">{post.title}</h1>
<PostContent content={post.body} />
{/* This one needs interactivity — so it's a Client Component */}
<CommentSection postId={post.id} />
</article>
)
}
Зверніце ўвагу на тое, чаго няма: ні useState, ні useEffect, ні обгорткі для маршрута API, ні калектавання інформацыі пра статус завантажэння. Данныя чакаюцца так сама, як і ў будзь-яй іншай async функцыі, што ёсць сутнёю данага модэлю.
Два практычныя прытамленьні. Пачынаючы з Next.js 15, params пасылаецца як Promise, таму сторанка павінна await params перш чым чытаць slug; пераканаўцеся, якая версія у вас ўстановлена. А калі рэальна ситуацыя з неадказам, вызов notFound() з next/navigation вяртае правільны статус 404, а не звычную сторанку з паведамленням пра адказ.
Поле для каментароў разлічнае. Яно зберагае текст чернавіку ў стане і рэагуе на набіранне тэксту і клікі, таму яго павінна выкананыць у браузеры. Дырэктыва "use client" у верхней часты файлу пазначае яго як Кліентскі компонент:
// components/CommentSection.tsx
"use client" // opts into browser rendering
import { useState } from "react"
type Props = {
postId: string
}
export function CommentSection({ postId }: Props) {
const [comment, setComment] = useState("")
const handleSubmit = async () => {
await fetch("/api/comments", {
method: "POST",
body: JSON.stringify({ postId, comment }),
})
setComment("")
}
return (
<div className="mt-8">
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
placeholder="Leave a comment..."
className="w-full border rounded p-2 text-sm"
/>
<button
onClick={handleSubmit}
className="mt-2 bg-blue-600 text-white px-4 py-2 rounded text-sm"
>
Post Comment
</button>
</div>
)
}
Усе інтэрактыўнае знаходзіцца тут: локальны стан для падлогі тексту, працоўнік змян і працоўнік адправкі, які надсылае даны на маршрут API і пасля чаго ачышчае поле. Калі настаўляеце гэта ў рэальных умовах, адправляйце заголовак Content-Type: application/json разам з запитам, і рассмотрзіце можлівасць викорыстання Server Action у замен на окольны маршрут API.
Такое раздзеленне лёгкае для розумэння: сервер контролюе даны, а кліент — інтэракцыю.
Правілы, якія падтрымваюць чыстасцю меж
- У App Router компаненты за замовчаннем ўжо являюцца Server Components.
- Дадзіце
"use client"толькі там, дзе вам патрэбны стан, эфекты, працоўнікі змян або API браузера, такія якwindowіlocalStorage. - Server Components можаюць імпортаваць і адрасаваць Client Components, як гэта робіць сторанка з
CommentSection.
children або іншыя пропсы, што дазволяе розмістваць контэнт, выробленаў на серверы, ўсередине інтэрактывной оболонкі."use client" таксама пазначае межу, а не адзін файл: усё, што імпортуецца гэтым файлам, стае часткай пакета кліента. Размешчэнне гэтага дырективы якомога нижэй у структуре, на маленькіх інтэрактывных элементах, дазволяе залишыць рэшту на серверы.
Заключанне
Калі задаць сабе пытанне „Дзе гэта будзе выконвана?“ як першае пытанне пра компанент, то ментальная модель ствараецца адразу: стандартныя адпаведзі Апп-Раутэра — це сервер.
- Компаненты сервера перамошчаюць процес адрасавання дадзейнаў і выконвання задач на сервер, таму не падаюць жадныя компаненты на JavaScript.
- Збір дадзейнаў стае простым
async/awaitунутранічна компаненту. - Вважаце
"use client"опцыяй для інтэрактыўных элементаў, а не стандартам. - Практычны першы крок: выберыце адну з компанентаў для збору дадзейнаў у існуючым проекте, пераканайцеся, чы рэальна яй патрэбны браузер, і пераканвертуйце яе, якщо ні.
Калі такая модэль будзе застосавана, структуры лейаутаў, тэхніка Suspense для стрімінгу, Server Actions і паралельныя маршруты стануць набліжэй да зрозумеласці, адтуды што кожны з іх будзе стварацца на адной і той жа базе, якая фокусуецца на серверы.
Спаднія матэрыялы
- Стварэнне стойкай сторонкі дакледжэння фільма з Next.js App Router — Дазвольце вы дазнаецеся, як правільна атрыбувацыя і кэшаванне дадзеных API OMDB у Next.js App Router за дапамою асінхронных Server Components, параметраў awaited і рэальнага адпаведзення на 404.
- Server Actions чы роут-хэндлеры? Карточка для выбору для Next.js 16 — Дазвольце вы дазнаецеся, калі мутацыя ў Next.js 16 павінна знаходзіцца ў Server Action, а калі яй патрэбны роут-хэндлеры, з правільнымі прыкладамі для форм, адзяўок, оптымізма і webhooks.