IlmHamroh
JavaScript Full-stack/23-qism. Next.js9/43-dars17 daqiqa
Mundarija (30)

React Server Components Next.js'da: serverda ishlaydigan komponentlar va RSC payload

Qisqacha: Next.js App Router'da har komponent sukut bo'yicha Server Component — u faqat serverda (yoki build vaqtida) ishlaydi va brauzerga o'z JS kodini yubormaydi. U async bo'la oladi: faylni o'qiydi, bazaga yoki API'ga so'rov yuboradi, process.env dagi sirni ishlatadi — va bularning hech biri brauzerga chiqmaydi. Evaziga unda useState, useEffect, onClick yo'q. Brauzerga natija ikki ko'rinishda boradi: tayyor HTML va RSC payload — komponent daraxtining matnli tasviri.

Bu darsda

  • Server Component qayerda va qachon ishlashini tushuntirasiz.
  • async komponentda faylni o'qiysiz — portfolio ning Markdown blogi.
  • Sirli kalit brauzerga chiqmasligini o'zingiz tekshira olasiz.
  • Server Component'da nima mumkin emasligini haqiqiy build xatolari bilan bilasiz.
  • RSC payload'ni o'qiy olasiz va marked kutubxonasi brauzerga 0 bayt qo'shganini ko'rasiz.

Oldin bilishingiz kerak: Parallel va intercepting routes, Fayl-asosli routing, Next.js nima, async / await.

1. Nega bu kerak?

Endi portfolio ga blog qo'shamiz. Postlar Markdown fayllarda (src/blog/*.md), ularni HTML'ga aylantirish uchun kutubxona kerak — kanon marked ni tanladi.

vazifalar (Vite SPA) da bu qanday bo'lardi? Kutubxona npm i qilinadi, import { marked } from "marked" yoziladi — va u brauzer to'plamiga (bundle) tushadi. Har foydalanuvchi uni yuklaydi, garchi postlar hech qachon o'zgarmasa ham. Markdown fayllarning o'zi ham brauzerga borishi kerak — fetch bilan yoki to'plam ichida.

Biz o'lchadik: marked ni brauzerda ishlaydigan komponentga qo'yganimizda u alohida JS bo'lagi bo'lib keldi — 13.3 KB gzip (45.7 KB xom). Bitta post sahifasi uchun.

Kanon esa xuddi shu blogni Server Component bilan qurdi. Natija: blog sahifasi boshqa sahifalardan atigi 0.7 KB og'ir — va bu 0.7 KB ham marked emas, «Ulashish» tugmasi. marked brauzerga 0 bayt keldi.

Qanday? Markdown'ni HTML'ga aylantirish ishi serverda bajarildi. Brauzerga natija — tayyor HTML — bordi, uni yasagan kod esa serverda qoldi.

Hayotiy o'xshatish: oshxona va zal. Restoranda osh oshxonada pishiriladi — qozon, o'tin, retsept oshxonada qoladi. Mijozga tayyor likopcha keladi. SPA'da esa mijoz stoliga qozon, guruch va retsept kitobi olib kelinadi: "o'zingiz pishiring".

2. Sukut bo'yicha — server

2.1 Siz allaqachon yozgansiz

Yangilik yo'qdek tuyulishi mumkin: Fayl-asosli routing darsidan beri yozgan har sahifangiz (AloqaSahifasi, LoyihaSahifasi) — Server Component. app/ dagi komponent fayl boshida "use client" bo'lmasa — server komponenti. Biz bu nomni ochiq aytmagan edik, xolos.

Server Component qachon ishlaydi?

Sahifa turi Server Component qachon chaqiriladi
Statik (○) Build vaqtida, bir marta; keyin tayyor HTML beriladi
Dinamik (ƒ, ◐ teshigi) Har so'rovda, serverda

Ikkalasida ham brauzerda emas. Statik sahifada komponent hatto foydalanuvchi kelganda ham ishlamaydi — u allaqachon next build paytida ishlab bo'lgan. Bu farqni Rendering strategiyalari darsida o'lchaymiz.

2.2 Ikki xil chiqish

Server Component'ning natijasi brauzerga ikki shaklda boradi:

flowchart LR
  S["Server Component<br/>(serverda ishlaydi)"] --> H["HTML<br/>birinchi ochilish"]
  S --> R["RSC payload<br/>daraxt matni"]
  H --> B["Brauzer: darhol<br/>ko'rinadi"]
  R --> B2["React: daraxtni<br/>yig'adi, o'tishlarda"]
  • HTML — birinchi ochilishda: matn darhol ko'rinadi (Next.js nima darsidagi "bo'sh vitrina" muammosining yechimi).
  • RSC payload — React daraxtining maxsus matn formati. React u bilan brauzerdagi daraxtni yig'adi va Navigatsiya darsidagi prefetch so'rovlari aynan shuni oladi.

Ikkalasida ham faqat natija bor, komponentning kodi yo'q. Pastda payload'ni ochib ko'ramiz.

3. async komponent: faylni to'g'ridan o'qish

3.1 Kanondagi blog

Oddiy React'da komponent async bo'la olmaydi — ma'lumot useEffect yoki kutubxona (TanStack Query) orqali keladi. Server Component'da esa async function va await — tabiiy yo'l. Kanondagi post sahifasi:

tsx
import { notFound } from "next/navigation";
import { UlashishTugmasi } from "@/komponentlar/UlashishTugmasi";
import { post, postlar } from "@/malumot/blog";
import { oyniYoz } from "@/malumot/sana";

export async function generateStaticParams() {
  return (await postlar()).map((p) => ({ slug: p.slug }));
}

export default async function PostSahifasi({
  params,
}: PageProps<"/blog/[slug]">) {
  const p = await post((await params).slug);
  if (!p) notFound();
  return (
    <main id="asosiy">
      <article>
        <h1>{p.sarlavha}</h1>
        <p className="ikkinchi">
          <time dateTime={p.sana}>{oyniYoz(p.sana)}</time> ·{" "}
          {p.teglar.join(", ")}
        </p>
        <div
          className="post-matni"
          dangerouslySetInnerHTML={{ __html: p.html }}
        />
      </article>
      <p>
        <UlashishTugmasi sarlavha={p.sarlavha} />
      </p>
    </main>
  );
}

await post(slug) — komponent ichida to'g'ridan. Yuklanish holati ham, useEffect ham yo'q. generateStaticParams ham async: postlar ro'yxatini fayllardan oladi (generateStaticParams darsida chuqur).

3.2 blog.ts — fayl tizimi komponent yonida

Ma'lumot qatlami — src/malumot/blog.ts. Uning asosiy qismi:

ts
import "server-only";
import { readdir, readFile } from "node:fs/promises";
import path from "node:path";
import { marked } from "marked";
import { cacheLife } from "next/cache";

const PAPKA = path.join(process.cwd(), "src", "blog");

export async function post(
  slug: string,
): Promise<(Post & { html: string }) | null> {
  "use cache";
  cacheLife("max");
  if (!/^[a-z0-9-]+$/.test(slug)) return null; // yo'l bilan o'ynash (../) yo'q
  try {
    const { maydonlar, tana } = frontmatter(
      await readFile(path.join(PAPKA, `${slug}.md`), "utf8"),
    );
    return { ...postga(slug, maydonlar), html: await marked.parse(tana) };
  } catch {
    return null;
  }
}

Qisqartirilgan: Post turi, frontmatter va postga yordamchilari, postlar() funksiyasi va ikki izoh qatori tushirildi. Kod qatorlari kanondagidek.

Qator Nima qiladi
import "server-only" Bu fayl brauzer komponentiga tushsa — build xatosi (Kompozitsiya)
node:fs/promises Node'ning fayl API'si — brauzerda yo'q, serverda bor
process.cwd() Loyiha papkasi; src/blog shu yerdan
"use cache" + cacheLife("max") Natija keshlanadi — pastda sababi
/^[a-z0-9-]+$/ Slug'da ../ bo'lsa — rad: boshqa faylni o'qitib bo'lmaydi
marked.parse Markdown → HTML, serverda

Slug tekshiruvi — kichik, lekin muhim xavfsizlik qatori. URL'dan kelgan slug to'g'ridan fayl yo'liga tushadi. Kimdir /blog/..%2F..%2F.env so'rasa-chi? Regex faqat kichik harf, raqam va - ga ruxsat beradi — . va / o'tmaydi. Server kodida URL'dagi har qiymat — begona ma'lumot.

frontmatter — post boshidagi --- orasidagi qatorlarni (sarlavha: …, sana: …) o'qiydigan o'z 10 qatorlik funksiya. Kanon gray-matter kutubxonasini olmadi: uning oxirgi relizi 2021-yilda bo'lgan (faol emas).

3.3 Natija oynasida: komponent — oddiy async funksiya

Natija oynasida server yo'q, async komponentni React u yerda chiza olmaydi. Lekin mohiyatini oddiy ts bilan ko'rish mumkin: Server Component — natijasi kutiladigan funksiya. Next.js uni chaqiradi, await qiladi va natijani HTML'ga aylantiradi. Bu yerda JSX o'rniga satr qaytaramiz:

ts
// "fayl tizimi" — xotirada
const FILES: Record<string, string> = {
  "salom.md": "# Salom\n\nBirinchi post.",
};

async function readFile(name: string): Promise<string> {
  await new Promise((r) => setTimeout(r, 50)); // disk kechikishi
  const text = FILES[name];
  if (text === undefined) throw new Error(`ENOENT: ${name}`);
  return text;
}

// Server Component o'rnida: async, ichida await
async function PostPage({ slug }: { slug: string }) {
  if (!/^[a-z0-9-]+$/.test(slug)) return "<h1>Topilmadi</h1>";
  const md = await readFile(`${slug}.md`);
  const title = md.split("\n")[0].replace("# ", "");
  return `<h1>${title}</h1>`;
}

PostPage({ slug: "salom" }).then(console.log);
PostPage({ slug: "../.env" }).then(console.log);

Konsolda:

text
<h1>Topilmadi</h1>
<h1>Salom</h1>

Tartibga qarang: ../.env natijasi birinchi chiqdi — u regex'da darhol rad etildi, await gacha yetmadi. salom esa 50 ms "diskni" kutdi. Next.js ham aynan shunday: har komponentni chaqiradi va Promise'lari tugashini kutadi.

Tekshirib ko'ring: readFile funksiyasi Server Component'da ishlatilgan. Nega node:fs brauzerga yuklanib, foydalanuvchi kompyuteridagi fayllarni o'qimaydi?

Javob

Server Component kodi brauzerga umuman bormaydi. readFile serverning (yoki build mashinasining) diskini o'qiydi — src/blog/ dagi postlarni. Brauzer faqat natijani (HTML va payload) oladi. Brauzerning o'zida node:fs yo'q ham.

4. Sirlar serverda qoladi

4.1 Biz tekshirdik

Server Component process.env dagi qiymatni bemalol o'qiydi. U brauzerga chiqmaydimi? Tekshirdik. Tajriba sahifasi:

tsx
export default function Sinov5() {
  const siri = process.env.SINOV_SIRI ?? "";
  return (
    <main>
      <p>Kalit uzunligi: {siri.length}</p>
    </main>
  );
}

SINOV_SIRI=maxfiy-kalit-777 npx next build, keyin butun .next/ papkasidan shu satrni qidirdik:

text
static: 0  server: 0
Kalit uzunligi: <!-- -->16<

.next/static (brauzerga beriladigan fayllar) — 0 ta fayl, .next/server — ham 0. HTML'da faqat natija: 16. Kalit faqat komponent ishlagan paytda, xotirada bo'ldi. <!-- --> — React'ning matn bo'laklari orasidagi belgisi.

Bitta muhim istisno: NEXT_PUBLIC_ bilan boshlangan o'zgaruvchilar ataylab brauzer to'plamiga yoziladi. Sirni hech qachon shunday nomlamang. Env qoidalari — Env o'zgaruvchilar darsida.

4.2 Sir va dangerouslySetInnerHTML

Post sahifasida dangerouslySetInnerHTML bor — Frontend xavfsizligi React'da darsidan bilasizki, bu xavfli joy. Kanon izohi aniq: matn — o'zimizning fayllarimiz, ishonchli manba. Agar postlar foydalanuvchidan kelsa (izohlar, mehmon postlari), HTML'ni tozalash (sanitizatsiya) shart bo'lardi. Server Component xavfsizlikni o'zi hal qilmaydi — u faqat kod qayerda ishlashini o'zgartiradi.

5. Server Component'ning cheklovlari

5.1 Nima yo'q

Server Component serverda bir marta ishlaydi va natija qaytaradi. Brauzerda u "tirik" emas — demak:

Mumkin emas Sababi O'rniga
useState, useReducer Holat brauzerda yashaydi Client komponent
useEffect, useLayoutEffect Effekt chizilgandan keyin brauzerda ishlaydi Client komponent
onClick, onChange Hodisa brauzerda Client komponent
window, localStorage Serverda brauzer API yo'q Client komponent
createContext Context — client daraxtda Provider client faylda

Mumkin: async/await, fayl va baza, sirlar, istalgan og'ir kutubxona, oddiy JSX, client komponentni ichida chizish.

5.2 Biz sinadik: haqiqiy xatolar

Sahifaga oddiy tugma qo'ydik: <button onClick={() => console.log("bosildi")}>. Build:

text
Error: Event handlers cannot be passed to Client Component props.
  {onClick: function onClick, children: ...}
            ^^^^^^^^^^^^^^^^
If you need interactivity, consider converting part of this to a Client Component.

Tarjimasi: "Hodisa ishlovchilarini Client Component prop'lariga uzatib bo'lmaydi. … Interaktivlik kerak bo'lsa, bir qismini Client Component'ga aylantiring". G'alati so'z: "Client Component prop'lari" — biz <button> yozdik-ku? Server uchun <button> ham brauzerda chiziladigan element: u onClick funksiyasini brauzerga yuborishi kerak bo'lardi. Funksiyani esa matnga aylantirib yuborib bo'lmaydi.

useState ning xatosini Loyiha yaratish darsida ko'rgansiz (You're importing a module that depends on useState into a React Server Component module). createContext ham aynan shu xabarni beradi — biz sinadik, faqat so'z boshqa: depends on createContext.

Ikki xato turi bor: hook importi — kompilyatsiya paytida (Turbopack fayl boshidan ko'radi), onClick — prerender paytida (komponent ishga tushganda). Ikkalasining davosi bitta — interaktiv qismni alohida client komponentga ajratish (Client Components darsida).

Tekshirib ko'ring: sahifada joriy yilni ko'rsatish kerak: {new Date().getFullYear()}. Server Component'da bu mumkinmi?

Javob

Kodning o'zi ishlaydi, lekin statik sahifada yil build vaqtida yoziladi va keyingi build'gacha o'zgarmaydi. Next 16 + Cache Components buni build xatosi bilan ushlaydi (unstable value new Date()) — kanon buni Hydration darsida ko'rsatadi. Tanlov: yilni matnda qoldirish, har so'rovda chizish yoki client'da hisoblash.

6. RSC payload: brauzerga nima boradi?

6.1 Kanon o'lchovi

Kanon /blog/server-components sahifasini ikki usulda so'radi: oddiy (HTML) va RSC: 1 sarlavhasi bilan (Next.js o'tishlarda shunday so'raydi):

text
HTML 12685 bayt (ichida self.__next_f.push: 8 ta), RSC 200 text/x-component 7528 bayt
marked/micromark nomi payload'da: false; post matni payload'da: true
  • HTML — 12 685 bayt. Ichida 8 ta self.__next_f.push(...) skripti: payload HTML'ning ichiga ham joylangan. Brauzer birinchi ochilishda payload uchun alohida so'rov yubormaydi.
  • RSC javobi — 7 528 bayt, turi text/x-component.
  • Payload'da marked so'zi yo'q, post matni bor. Kutubxona emas, uning natijasi bordi.

6.2 Payload ichida

Xom payload'dan bir nechta qator (to'liq fayl 7 528 bayt):

text
5:I[35490,["$2"],"NavHavola"]
a:I[61173,["$2","$3"],"OrqagaTugmasi"]
25:I[45089,["$2","$24"],"UlashishTugmasi"]
11:["$","main",null,{"id":"asosiy","children":[["$","article",null,{"children":[["$","h1",null,{"children":"Server Components — JavaScript'siz React"}],…
…["$","$L25",null,{"sarlavha":"Server Components — JavaScript'siz React"}]

Oxirgi ikki qator qisqartirilgan (…). O'qishni o'rganamiz:

Bo'lak Ma'nosi
5: Qator raqami (16 lik sanoqda) — boshqa qatorlar unga $5 deb murojaat qiladi
I[35490,[…],"NavHavola"] Client komponentga havola: modul raqami, JS fayllari, eksport nomi
["$","h1",null,{"children":…}] Oddiy element: tur, kalit, props — tayyor matni bilan
["$","$L25",null,{"sarlavha":…}] 25-qatordagi client komponent (UlashishTugmasi) va unga props

Server komponentlar (PostSahifasi, LoyihaKartasi) payload'da umuman ko'rinmaydi — faqat ular qaytargan elementlar (main, article, h1). Client komponentlar esa nom bilan, "bu yerga shu faylni yuklab, shu props bilan chiz" degan havola bo'lib turadi. Brauzer faqat shu havolalarning JS'ini yuklaydi.

$L — "lazy" (kechiktirilgan) havola: komponent kodi alohida faylda, kerak bo'lganda yuklanadi.

Bu format — React'ning ichki tafsiloti, uni qo'lda yozmaysiz. Lekin uni o'qiy olish foydali: DevTools'ning Network panelida ?_rsc= so'rovlarini ochsangiz, "nega bu sahifa og'ir?" degan savolga javob ko'rinadi.

6.3 Raqamlar: brauzer JS

Sahifa JS (gzip) Farqi
Boshqa sahifalar 137.9 KB —
/blog/server-components 138.6 KB +0.7 KB (UlashishTugmasi)
marked client komponentda (biz sinadik) alohida bo'lak 13.3 KB butun kutubxona

Kutubxona tanlovi ham o'lchandi: marked 18.1.1 — 540 KB, 1 paket; @next/mdx + loader — 9.8 MB, 104 paket. Ikkalasi ham faqat serverda ishlaydi, brauzerga farqi yo'q — lekin node_modules va build tezligiga bor. Postlarda React komponenti kerak emas edi, shuning uchun kanon marked ni tanladi.

7. Biz topdik: fayl o'qish ham "keshlanmagan ma'lumot"

blog.ts dagi "use cache" qatori nega kerak? Kanon avval usiz build qildi:

text
Error: Route "/blog": Next.js encountered uncached or runtime data during prerendering.
`fetch(...)`, `cookies()`, `headers()`, `params`, `searchParams`, or `connection()` accessed outside of `<Suspense>` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.
Ways to fix this:
  - [stream] Provide a placeholder with `<Suspense fallback={...}>` around the data access
  - [cache] For uncached data (`fetch`, database calls): cache the access with `"use cache"` (does not apply to `connection()`)
  - [block] Set `export const instant = false` to allow a blocking route

Cache Components rejimida Next.js har asinxron ma'lumot manbasini — fetch, baza, hatto disk — "o'zgarishi mumkin" deb hisoblaydi. U sahifani oldindan chizishdan oldin aniq bilishni xohlaydi: bu ma'lumot keshlansinmi ([cache]), har so'rovda kutilsinmi ([stream]) yoki sahifa umuman bloklovchi bo'lsinmi ([block]).

Postlar faqat deploy bilan o'zgaradi — kanon [cache] ni tanladi: "use cache" + cacheLife("max"). Build jadvalida yangi ustunlar paydo bo'ldi:

text
Route (app)                         Revalidate  Expire
├ ○ /blog                                  30d      1y
│ ├ ○ /blog/server-components              30d      1y

30d / 1y — max profilining muddatlari. "use cache" ning o'zi — Cache Components I darsida. Bugun bilish yetarli: Server Component'da ma'lumot olish — oson, lekin Next 16 "qachon yangilanadi?" degan savolni javobsiz qoldirmaydi.

8. Portfolio qadami: blog (1-qism)

Bu dars, Client Components va Kompozitsiya darslari portfolio ning holat 11 ini quradi. Commit hali qilmang — u uchinchi darsda.

Fayl Nima
src/blog/*.md 3 post: frontmatter (sarlavha, sana, tavsif, teglar) + Markdown
src/malumot/blog.ts server-only, fs, o'z frontmatter tahlili, marked, "use cache"
app/(sayt)/blog/page.tsx Ro'yxat — async Server Component
app/(sayt)/blog/[slug]/page.tsx Post — generateStaticParams, notFound()
komponentlar/SaytSarlavhasi.tsx Menyuga «Blog»
bash
npm i --save-exact marked@18.1.1 server-only@0.0.1

Build jadvalida ○ /blog va uchta ○ /blog/<slug> (30d/1y), ◐ /blog/[slug] — noma'lum slug uchun qobiq. Postlaringiz — o'zingizniki: o'rgangan narsangiz haqida uchta qisqa yozuv.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Event handlers cannot be passed to Client Component props Server Component'da onClick Tugmani client komponentga ajrating
… depends on useState / createContext into a React Server Component Hook yoki Context serverda "use client" li alohida fayl
uncached or runtime data during prerendering Fayl/fetch keshsiz, Suspense'siz "use cache" yoki <Suspense>
ReferenceError: window is not defined Server kodida brauzer API Brauzer API — client komponentda, effekt ichida
Sir brauzerda ko'rindi NEXT_PUBLIC_ prefiksi Sirni prefikssiz nomlang
/blog/..%2F.env fayl o'qidi Slug tekshirilmagan Qat'iy regex (^[a-z0-9-]+$)

10. Mashqlar

Mashqlar kurs/mashqlar/23/09-server-components/ da.

1-mashq (oson): server yoki client?

  • Postni Markdown'dan HTML'ga aylantirish —
  • «Nusxalash» tugmasi (navigator.clipboard) —
  • process.env.GITHUB_TOKEN bilan API so'rovi —
  • Hisoblagich (useState) —
  • Loyihalar ro'yxatini massivdan chizish —
Yechim
  • server — kutubxona brauzerga bormaydi, natija HTML bo'lib keladi.
  • client — brauzer API va bosish hodisasi.
  • server — token faqat serverda xavfsiz.
  • client — holat brauzerda yashaydi.
  • server — interaktivlik yo'q; sukut bo'yicha server, JS 0.

2-mashq (o'rta): frontmatter tahlili

Kanondagidek o'z frontmatter(text) funksiyangizni yozing: --- orasidagi kalit: qiymat qatorlarini obyektga yig'ing, qolgan matnni body deb qaytaring. Frontmatter bo'lmasa — { fields: {}, body: text }. Qiymat ichida : bo'lishi mumkin (tavsif: Next.js: birinchi qadam) — faqat birinchi : bo'yicha bo'ling. Ishora: indexOf va slice.

Yechim
js
function frontmatter(text) {
  const m = /^---\n([\s\S]*?)\n---\n/.exec(text);
  if (!m) return { fields: {}, body: text };
  const fields = {};
  for (const line of m[1].split("\n")) {
    const i = line.indexOf(":");
    fields[line.slice(0, i).trim()] = line.slice(i + 1).trim();
  }
  return { fields, body: text.slice(m[0].length) };
}

const post = `---
sarlavha: Salom
tavsif: Next.js: birinchi qadam
---
# Salom`;

const { fields, body } = frontmatter(post);
console.log(fields.sarlavha);
console.log(fields.tavsif);
console.log(body);
console.log(frontmatter("# Frontmattersiz").fields);

Konsolda:

text
Salom
Next.js: birinchi qadam
# Salom
{}

indexOf(":") birinchi ikki nuqtani topadi — tavsif qiymatidagi Next.js: buzilmadi. Kanon versiyasi \r?\n ham qabul qiladi: Windows'da saqlangan fayllarda qator oxiri \r\n bo'ladi.

3-mashq (qiyin): payload modelini yasang

RSC payload'ning soddalashtirilgan modeli: toPayload(node) — daraxtni oladi va qatorlar massivini qaytaradi. Tugun — satr (matn) yoki { type, props, children }. type — satr ("h1") bo'lsa — oddiy element; { client: "Nom" } bo'lsa — client komponent: unga alohida qator N:I["Nom"] yoziladi va o'rnida "$LN" turadi. Funksiya bo'lsa (type: function) — bu Server Component: uni chaqirib, natijasini yozing (payload'da server komponent ko'rinmaydi). Oxirgi qator — 0: + daraxt JSON'i.

Yechim
js
function toPayload(root) {
  const rows = [];
  let next = 1;
  function walk(node) {
    if (typeof node === "string") return node;
    if (typeof node.type === "function") {
      return walk(node.type(node.props)); // server: chaqiramiz
    }
    const children = (node.children ?? []).map(walk);
    if (typeof node.type === "object") {
      const id = next++;
      rows.push(`${id}:I["${node.type.client}"]`);
      return ["$", `$L${id}`, node.props ?? {}, children];
    }
    return ["$", node.type, node.props ?? {}, children];
  }
  const tree = walk(root);
  rows.push(`0:${JSON.stringify(tree)}`);
  return rows;
}

const Share = { client: "UlashishTugmasi" };
function PostPage(props) {
  return {
    type: "main",
    children: [
      { type: "h1", children: [props.title] },
      { type: Share, props: { sarlavha: props.title } },
    ],
  };
}

const rows = toPayload({ type: PostPage, props: { title: "Salom" } });
for (const row of rows) console.log(row);

Konsolda:

text
1:I["UlashishTugmasi"]
0:["$","main",{},[["$","h1",{},["Salom"]],["$","$L1",{"sarlavha":"Salom"},[]]]]

PostPage payload'da yo'q — u chaqirildi va o'rnida natijasi (main) qoldi. UlashishTugmasi esa havola ($L1) va props bo'lib qoldi. Haqiqiy format murakkabroq (modul raqamlari, JS fayllar ro'yxati, kalitlar), lekin g'oyasi shu: server komponentlar "eriydi", client komponentlar "manzil" bo'lib qoladi.

4-mashq: Portfolio qadami

O'z portfolio ingizga blog qo'shing: src/blog/ da 3 ta post, src/malumot/blog.ts (server-only, fs, marked, slug tekshiruvi, "use cache"), ro'yxat va post sahifalari, menyuda «Blog». Tekshiring: npm run build jadvalida /blog 30d/1y bilan; next start da DevTools Network panelida JS fayllar orasida marked kodi yo'qligini toping (Search: marked).

Yechim

Namuna — «Portfolio qadami» bo'limidagi jadval va kanon blog.ts. Build uncached or runtime data bilan to'xtasa — "use cache" qatori funksiyaning birinchi qatori bo'lishi kerak (direktiva, "use client" kabi). Network'da marked topilsa — uni qaysidir client komponent import qilgan (Client Components darsida sababini ko'ramiz). Commit hali qilmang.

11. Real ishda

  • Do'kon va kontent saytlari — mahsulot sahifasi Server Component: bazadan narx va tavsif, brauzerga faqat «Savatga» tugmasining JS'i. Katta katalogda JS farqi o'nlab kilobayt.
  • Sirlar — to'lov tizimi kaliti, baza paroli, AI API tokeni — hammasi Server Component va server kodida; brauzerga hech qachon. Intervyuda "API kalitini frontend'da qanday yashirasiz?" savolining zamonaviy javobi shu.
  • RSC payload'ni o'qish — sahifa nega og'irligini topishda: payload'da kutilmagan katta ma'lumot (butun mahsulotlar ro'yxati) client komponentga prop bo'lib ketayotgan bo'lishi mumkin.
  • React Server Components — faqat Next.js'niki emas: React 19 ning qismi, boshqa freymvorklar ham qo'llab-quvvatlashni boshlagan.

Xulosa

  • app/ dagi komponent sukut bo'yicha Server Component: build vaqtida yoki serverda ishlaydi, brauzerga kodi bormaydi.
  • async bo'la oladi: fayl, baza, API va sirlar to'g'ridan — marked brauzerga 0 bayt, sir HTML'da faqat natija.
  • useState, useEffect, onClick, window, createContext — yo'q; xatolari aniq, davosi — client komponent.
  • Brauzerga HTML va RSC payload boradi: server komponentlar eriydi, client komponentlar I[…] havola bo'lib qoladi.
  • Cache Components'da fayl o'qish ham "keshlanmagan ma'lumot" — "use cache" yoki <Suspense> tanlanadi.

Keyingi dars: Client Components va "use client" chegarasi — «Ulashish» tugmasi qanday qilib brauzerda jonlanadi va "use client" qatori nimani "yuqtiradi".

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
React Server Components Next.js'da: serverda ishlaydigan komponentlar va RSC payload — IlmHamroh