Irbisa · Шпаргалка

Server Components и Next.js

React Server Components, серверные функции, CSR, SSR, SSG и ISR, стриминг, ошибки гидратации, App Router в Next.js, кэширование

Senior-разработчик · 22 вопроса

  1. 01

    Что такое React Server Components и чем они отличаются от клиентских компонентов?

    Серверные компоненты рендерятся только на сервере (при сборке или на каждый запрос), могут быть async и напрямую читать базу данных или файлы, а в браузер попадают как сериализованный UI без JavaScript.

    // app/posts/[id]/page.tsx: a Server Componentexport default async function PostPage({ params }: { params: Promise<{ id: string }> }) {  const { id } = await params;  const post = await db.post.findUnique({ where: { id } });  if (!post) notFound();…
  2. 02

    Чем отличаются CSR, SSR, SSG и ISR и когда выбирать каждый вариант?

    Они отличаются тем, где и когда получается HTML: CSR собирает его в браузере из JavaScript, SSR на сервере для каждого запроса, SSG один раз при сборке, а ISR при сборке с фоновой перегенерацией страниц по таймеру или по запросу.

  3. 03

    Как устроены файловые соглашения App Router: page, layout, loading, error и not-found?

    В App Router каждая папка внутри app/ соответствует сегменту URL, а специальные файлы в ней задают UI этого сегмента: page.tsx делает его доступным по адресу, layout.tsx оборачивает его и сохраняется между переходами, а loading.tsx и error.tsx добавляют вокруг него границу Suspense и error boundary.

    // app/dashboard/error.tsx'use client'; export default function DashboardError({  error,  reset,…
  4. 04

    Что на самом деле помечает "use client" и где ставить эту границу?

    "use client" в начале файла помечает границу в графе модулей: этот модуль и всё, что он импортирует, становятся клиентским кодом, который попадает в бандл и гидрируется в браузере.

    // app/products/page.tsx: Server Componentexport default async function ProductsPage() {  const products = await getProducts();  return (    <FilterPanel>      {/* rendered on the server, passed through the client boundary */}…
  5. 05

    Что серверный компонент может передать клиентскому в пропсах, а что вызовет ошибку?

    Пропсы, которые пересекают границу, должны сериализоваться средствами React: примитивы, обычные объекты и массивы, Date, Map, Set, типизированные массивы, промисы, JSX и серверные функции.

    // app/posts/[id]/page.tsx: Server Componentexport default async function PostPage({ params }: { params: Promise<{ id: string }> }) {  const { id } = await params;  const commentsPromise = getComments(id); // not awaited: the page streams on  return (    <Suspense fallback={<p>Loading comments...</p>}>…
  6. 06

    Найдите баг: страница падает с ошибкой «Event handlers cannot be passed to Client Component props.»

    // app/cart/page.tsxexport default async function CartPage() {  const cart = await getCart();   return (    <div>      <p>{cart.items.length} items</p>      <button onClick={() => console.log('checkout', cart.id)}>        Checkout      </button>    </div>  );}

    CartPage является серверным компонентом, а серверный компонент не может навешивать обработчики событий: функцию нельзя сериализовать в RSC payload, и ни строчки кода этого компонента не выполняется в браузере, чтобы её вызвать.

  7. 07

    Как в приложении на Server Components не допустить попадания секретов и серверного кода в клиентский бандл?

    Код, который импортируют только серверные компоненты, на клиент не попадает, но один случайный импорт из клиентского компонента затянет его в бандл.

    // lib/data/users.tsimport 'server-only'; export async function getProfile(userId: string) {  const user = await db.user.findUnique({ where: { id: userId } });  if (!user) return null;…
  8. 08

    Что такое Server Functions (Server Actions) и как вызвать такую функцию из формы?

    Server Function представляет собой async-функцию с директивой "use server", которая выполняется на сервере, но вызывается с клиента: React отправляет аргументы POST-запросом и возвращает результат.

    // app/todos/actions.ts'use server'; export async function createTodo(  _prev: { error?: string },  formData: FormData,…
  9. 09

    Что такое Route Handlers (route.ts) в App Router, и когда использовать их вместо Server Action?

    Файл route.ts экспортирует функции с именами HTTP-методов (GET, POST, DELETE), которые принимают Request и возвращают Response, то есть описывает обычный HTTP-эндпоинт по этому URL.

    // app/api/webhooks/payments/route.ts: called by the payment provider, not by your UIexport async function POST(request: Request) {  const body = await request.text(); // the raw body: the signature is computed over it  const signature = request.headers.get('x-signature') ?? '';  if (!(await verifySignature(body, signature, process.env.PAYMENTS_WEBHOOK_SECRET!))) {    return new Response('Invalid signature', { status: 400 });…
  10. 10

    Что делает proxy.ts (бывший middleware.ts) в Next.js 16, и что в него не стоит класть?

    proxy.ts в корне проекта экспортирует одну функцию, которая выполняется до того, как запрос дойдёт до маршрута, и может сделать редирект или rewrite, выставить заголовки и cookie или ответить сама.

    // proxy.ts (Next.js 16; formerly middleware.ts with `export function middleware`)import { NextResponse, type NextRequest } from 'next/server'; export function proxy(request: NextRequest) {  // optimistic check: no cookie means no session, so do not even render the page  if (request.nextUrl.pathname.startsWith('/account') && !request.cookies.has('session')) {…
  11. 11

    Как загружать данные в серверных компонентах и как избежать водопада запросов?

    Сделать компонент async и напрямую делать await данных (запрос к базе, ORM или fetch), без useEffect и без клиентского API-слоя.

    export default async function DashboardPage() {  // both requests start before either is awaited  const [user, orders] = await Promise.all([getUser(), getOrders()]);   return (    <>…
  12. 12

    Как работает потоковый SSR (streaming) с Suspense и loading.tsx?

    При стриминге сервер отправляет HTML частями в одном ответе: сначала оболочку с fallback-ами Suspense, затем каждую приостановленную часть по мере готовности, в виде HTML и маленького встроенного скрипта, который ставит её на место.

    // app/product/[id]/page.tsxexport default async function ProductPage({ params }: { params: Promise<{ id: string }> }) {  const { id } = await params;  const product = await getProduct(id); // fast: part of the first chunk   return (…
  13. 13

    Что вызывает ошибку несовпадения при гидратации (hydration mismatch) и как её исправить?

    Гидратация подключает React к HTML, отрендеренному на сервере, и ожидает, что первый клиентский рендер даст ту же разметку.

  14. 14

    Найдите баг: кнопка темы выдаёт ошибку гидратации, но только у пользователей, выбравших тёмную тему.

    // ThemeToggle.tsx'use client'; export function ThemeToggle() {  const saved = typeof window !== 'undefined' ? localStorage.getItem('theme') : null;  const [theme, setTheme] = useState(saved ?? 'light');   return (    <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>      {theme === 'dark' ? 'Light mode' : 'Dark mode'}    </button>  );}

    Первый рендер читает localStorage только в браузере, поэтому сервер рендерит «Dark mode», а браузер пользователя с тёмной темой при гидратации рендерит «Light mode», и текст не совпадает.

  15. 15

    Когда вы выберете клиентское SPA (Vite и React Router), а не Next.js с серверными компонентами?

    SPA стоит выбрать, когда SEO и скорость первой загрузки для анонимных посетителей не важны, а приложение очень интерактивное и живёт за логином: админка, внутренний инструмент, редактор.

  16. 16

    Как настроить SEO в App Router из Next.js: metadata, generateMetadata, sitemap.ts и канонические адреса страниц?

    Экспортируйте из page или layout статический объект metadata или асинхронную generateMetadata, если теги зависят от параметров или данных; Next.js превращает их в теги <title>, <meta> и <link> в серверном HTML.

    // app/layout.tsxexport const metadata: Metadata = {  metadataBase: new URL('https://shop.example'),  title: { template: '%s | Shop', default: 'Shop' },};…
  17. 17

    Почему каждый Server Action по сути публичный эндпоинт и как его защитить?

    React превращает каждую серверную функцию в HTTP-эндпоинт с id, который клиент отправляет в POST-запросе, и вызвать его может кто угодно с любыми аргументами, а не только ваша форма.

    // app/posts/actions.ts'use server'; const RenameInput = z.object({ postId: z.string().min(1), title: z.string().min(1).max(200) }); export async function renamePost(formData: FormData) {…
  18. 18

    Как устроено кэширование в Next.js App Router и что изменилось в Next.js 15 и 16?

    Next.js кэширует на нескольких уровнях: мемоизация запросов в пределах одного рендера, Data Cache для результатов между запросами, Full Route Cache для пререндеренных страниц и клиентский Router Cache.

    import { cacheLife, cacheTag } from 'next/cache'; export async function getProducts(category: string) {  'use cache';  cacheLife('hours');  cacheTag('products', `products-${category}`);…
  19. 19

    Как после мутации заставить App Router показать свежие данные: revalidatePath, revalidateTag, updateTag или router.refresh?

    Инвалидировать записи кэша, которые запись в базу сделала устаревшими, прямо из серверной функции, которая её выполнила: revalidateTag для всех закэшированных чтений с этим тегом, revalidatePath для одного роута.

    'use server'; import { revalidateTag, updateTag } from 'next/cache'; export async function publishPost(postId: string) {  await requireEditor();…
  20. 20

    Что делает cache() из React в серверных компонентах и чем он отличается от "use cache" в Next.js?

    cache(fn) из React мемоизирует функцию на время одного серверного запроса: компоненты, которые вызывают getUser(id) с одинаковыми аргументами, делят один вызов и один результат.

    import { cache } from 'react'; export const getUser = cache(async (id: string) => {  return db.user.findUnique({ where: { id } });});…
  21. 21

    Найдите баг: после деплоя пользователи иногда видят в шапке страницы имя другого пользователя.

    // lib/current-user.tslet currentUser: User | null = null; // the layout, the header and the page all call it: query the database only onceexport async function getCurrentUser() {  if (!currentUser) {    const session = await auth();    currentUser = await db.user.findUnique({ where: { id: session.userId } });  }  return currentUser;} // app/Header.tsxexport async function Header() {  const user = await getCurrentUser();  return <p>Signed in as {user?.name}</p>;}

    currentUser живёт в области видимости модуля, а серверный модуль загружается один раз на процесс и используется всеми запросами: первый посетитель после старта кэшируется, и все последующие на этом инстансе получают его имя.

  22. 22

    Что сервер отправляет для серверных компонентов (RSC payload) и как клиент использует это при навигации?

    RSC payload (формат «Flight») представляет собой потоковую сериализацию отрендеренного дерева: элементы, текст, пропсы клиентских компонентов и ссылки на их JavaScript-чанки вместо самого кода.