Галоўная / Артыкулы / Дзе павінна быть лінія сервер-кліент у сторанцы Next.js App Router

Дзе павінна быть лінія сервер-кліент у сторанцы Next.js App Router

Практычны модель разуму для React Server Components: што запускаецца дзе, як сторніца пасты ў блогу дзеліцца на часткі сервера і кліента, а таксама правілы імпорту, якія трэба дапэўнаваць.

1321 слоў

Компаненты сервера у 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 або іншыя пропсы, што дазволяе розмістваць контэнт, выробленаў на серверы, ўсередине інтэрактывной оболонкі.
  • Пропсы, якія пераходзяць з сервера да кліента, павінны быць серыялізаванымі: простыя даны падходзяць, а функцыі і экземпляры класаў — няма.
  • Стылізаванне не падвергаецца змянам. Класы Tailwind і CSS працуюць аднойчыны ў обох видах компанентаў.
  • "use client" таксама пазначае межу, а не адзін файл: усё, што імпортуецца гэтым файлам, стае часткай пакета кліента. Размешчэнне гэтага дырективы якомога нижэй у структуре, на маленькіх інтэрактывных элементах, дазволяе залишыць рэшту на серверы.

    Заключанне

    Калі задаць сабе пытанне „Дзе гэта будзе выконвана?“ як першае пытанне пра компанент, то ментальная модель ствараецца адразу: стандартныя адпаведзі Апп-Раутэра — це сервер.

    • Компаненты сервера перамошчаюць процес адрасавання дадзейнаў і выконвання задач на сервер, таму не падаюць жадныя компаненты на JavaScript.
    • Збір дадзейнаў стае простым async/await унутранічна компаненту.
    • Вважаце "use client" опцыяй для інтэрактыўных элементаў, а не стандартам.
    • Практычны першы крок: выберыце адну з компанентаў для збору дадзейнаў у існуючым проекте, пераканайцеся, чы рэальна яй патрэбны браузер, і пераканвертуйце яе, якщо ні.

    Калі такая модэль будзе застосавана, структуры лейаутаў, тэхніка Suspense для стрімінгу, Server Actions і паралельныя маршруты стануць набліжэй да зрозумеласці, адтуды што кожны з іх будзе стварацца на адной і той жа базе, якая фокусуецца на серверы.

    Спаднія матэрыялы

  • Перайшоўцы з next/router: Params, Shallow URLs і 404-е сторанцы ў App Router — Дазнаёцесь, як params, useParams, апдэты шальонаў URL, програмная навігацыя і справжнія сторанцы 404 працуюць у Next.js App Router пасля знячынення next/router.
  • Тэхнічны SEO ў Next.js App Router: Metadata, Sitemaps і JSON-LD — Дазнаёцесь, як спяльныя памагчыкі metadata, стандартныя настройкі корневай структуры, файл robots.ts, дынамічны сітамап, правдзівы JSON-LD і аудыты сторанак ствараюць чыстую базу для SEO ў аплікацыях Next.js.
  • Чаму WebSockets застаюцца ў Next.js API маршрутах і як це паспелівае спецыяльны сервер — Дазвольце дазнацца, чаму сервер ws унутры маршрута pages/api ніколі не завершае процэс аб’явлення звязку, і як самі разабрацца з адбываннем падзеi upgrade за дапамою спецыяльнага сервера Next.js.