IlmHamroh
JavaScript Full-stack/21-qism. React: media, integratsiyalar, PWA31/32-dars20 daqiqa
Mundarija (30)

React ilova yuklanishini optimallashtirish: JS byudjeti, sahifalarni kech yuklash, shriftlar va resurslar

Qisqacha: Birinchi yuklashda brauzer qancha kam JS olsa, sahifa shuncha tez ishlaydi. Avval byudjet qo'ying ("birinchi yuklash ≤ N KB") va har build'da tekshiring. Keyin ro'yxatdan boshqa sahifalarni kech yuklang (router lazy). Resurslarga maslahat bering (preload, prefetch, preconnect), shriftni to'g'ri ulang (font-display, subset yoki tizim shrifti — 0 KB). Server esa brotli va HTTP/2–3 bersin. vazifalar da 21-qismda birinchi yuklash 225.2 KB gacha o'sdi; sahifalarni kech yuklash uni 206.6 KB ga tushirdi (−18.6), Lighthouse mobil 86/89 → 88/92.

Bu darsda

  • Birinchi yuklash byudjetini qo'yasiz va uni avtomatik tekshiradigan skript yozasiz.
  • React Router lazy bilan sahifalarni alohida chunk'larga ajratasiz va "Yuklanmoqda…" narxini tushunasiz.
  • preload, modulepreload, prefetch va preconnect ni React ilovasida qayerda ishlatishni bilasiz.
  • Web shriftni tez yuklash usullarini (font-display, subset, @fontsource) va tizim shriftini solishtirasiz.
  • Brotli, HTTP/2–3 va kesh sarlavhalarining ta'sirini, Lighthouse CI bilan nazoratni tushunasiz.

Oldin bilishingiz kerak: Code splitting va hash bilan keshlash, Vite V: production build va bundle tahlili, lazy va code splitting, Resurs maslahatlari, Web shriftlar, Lighthouse va PageSpeed, Core Web Vitals.

1. Nega bu kerak?

21-qism vazifalar ga ko'p narsa qo'shdi: to'rt til, Markdown izoh, QR, analitika, oflayn navbat. Har biri "kichkina". Natija — birinchi yuklashdagi JS o'sib bordi:

vazifalar: birinchi yuklash JS (gzip)
  • v10 (20-qism oxiri)201,8 KB
  • holat 02 (i18n)219 KB
  • holat 26 (AI chat)224,1 KB
  • holat 29 (oflayn navbat)21-qism cho'qqisi225,2 KB
  • holat 31 (sahifalar kech)−18.6 KB206,6 KB

Manba: O'lchandi: kanon, tekshiruv-02/26/29/31.txt va v10 o'lchovi, gzip -9, 2026-10-17

Grafikka qarang: 2-holatdagi sakrash — i18n (+17.1 KB — i18n I darsida ongli qabul qilingan narx). Keyin har dars ozgina qo'shdi. Hech biri alohida "katta" emas edi — shuning uchun hech kim to'xtatmadi. Bu — eng keng tarqalgan yo'l: ilova bir kunda emas, yuz kichik qadamda sekinlashadi.

Aziz telefonda, sekin internetda o'qiydi. Unga 225 KB gzip JS — yuklab olish, ochish (decompress), tahlil qilish va bajarish. O'rta telefonda bu bir necha soniya. Bu darsda o'sishni to'xtatamiz va qaytaramiz: avval o'lchov va byudjet, keyin eng katta yutuq — sahifalarni kech yuklash, oxirida shriftlar va server.

Hayotiy o'xshatish: safar sumkasi. Hamma narsani olsangiz — og'ir. Yaxshi sayohatchi faqat birinchi kunga kerakini oladi, qolganini yo'lda, kerak bo'lganda sotib oladi.

2. Byudjet: "qancha" ni oldindan aytish

2.1 Nimani o'lchaymiz

Unumdorlik byudjeti (performance budget) — ilova oshib o'tmasligi kerak bo'lgan chegara: "birinchi yuklash JS ≤ 210 KB gzip", "LCP ≤ 2.5 s". Byudjetsiz har yangi kutubxona "atigi 5 KB" bo'lib ko'rinadi. Byudjet bilan esa savol o'zgaradi: "5 KB ni qayerdan bo'shatamiz?".

vazifalar 16-qismdan beri har holatda bir xil o'lchaydi (tekshiruv-NN.txt): har fayl — xom, gzip -9 va brotli; * — birinchi yuklashda keladigan fayllar. Holat 31 dan parcha:

text
assets/index-CKFkeGF8.js *                619862  200782  172219
assets/index-aWHIrYMM.css *                21327    5101    4592
assets/react-DB-4Zxce.js *                  8572    3321    2975
assets/StatistikaSahifasi-rMf5R83c.js       1389     644     596
assets/zod-B9zOeopa.js                     43006   14597   13372
JAMI birinchi js                          633826  206596  177383

Ustunlar: xom bayt, gzip, brotli. Asosiy fayl — 620 KB xom, 201 KB gzip. StatistikaSahifasi va zod da yulduzcha yo'q — ular faqat kerak bo'lganda keladi.

Nega gzip hajmi? Chunki tarmoqdan aynan shu o'tadi. Lekin unutmang: brauzer xom 620 KB ni tahlil qiladi va bajaradi — telefonda bu tarmoqdan ham qimmat bo'lishi mumkin. Gzip — yuklash narxi, xom — bajarish narxi.

2.2 Byudjet skripti

Byudjetni har build'dan keyin tekshiradigan kichik skript. Ma'lumot — holat 29 va 31 ning haqiqiy raqamlari:

js
const BYUDJET = { js: 210_000, css: 6_000 }; // gzip bayt

function tekshir(nom, hajm) {
  const xatolar = [];
  for (const [tur, chegara] of Object.entries(BYUDJET)) {
    const bor = hajm[tur];
    const foiz = Math.round((bor / chegara) * 100);
    const belgi = bor > chegara ? "✘" : "✔";
    if (bor > chegara) xatolar.push(tur);
    const matn = `${nom} ${tur}: ${bor} / ${chegara} (${foiz}%)`;
    console.log(`${belgi} ${matn}`);
  }
  return xatolar.length === 0;
}

const h29 = tekshir("holat 29", { js: 225_197, css: 5_101 });
const h31 = tekshir("holat 31", { js: 206_596, css: 5_101 });
console.log({ h29, h31 });

Konsolda:

text
✘ holat 29 js: 225197 / 210000 (107%)
✔ holat 29 css: 5101 / 6000 (85%)
✔ holat 31 js: 206596 / 210000 (98%)
✔ holat 31 css: 5101 / 6000 (85%)
{ h29: false, h31: true }

Haqiqiy loyihada skript dist/ ni o'qiydi (fs bilan) va byudjetdan oshsa process.exitCode = 1 qiladi — npm run build dan keyin npm run byudjet, CI'da ham. 210 KB tanlovi ixtiyoriy emas: v10 201.8 KB edi, i18n qarori +17 KB ga ruxsat berdi; byudjet "bundan buyon faqat kamaysin" degan chegara.

2.3 Lighthouse CI: byudjet va ball avtomatik

Lighthouse CI (@lhci/cli) — Lighthouse ni har commit'da ishga tushirib, natijani chegaralar bilan solishtiradi. Hujjat bo'yicha sozlama (lighthouserc.json):

json
{
  "ci": {
    "collect": { "staticDistDir": "./dist" },
    "assert": {
      "assertions": {
        "categories:performance": ["error", { "minScore": 0.85 }],
        "categories:accessibility": ["error", { "minScore": 1 }],
        "largest-contentful-paint": [
          "warn",
          { "maxNumericValue": 3000 }
        ],
        "total-byte-weight": ["warn", { "maxNumericValue": 400000 }]
      }
    }
  }
}

error — CI to'xtaydi, warn — ogohlantiradi. staticDistDir — dist/ ni o'zi kichik serverda ochadi. Kanon Lighthouse CI'ni ishlatmadi: kursning CI'i pulsiz va qo'lda (lokal tekshiruv). Kanon Lighthouse'ni o'zi chaqiradigan skript yozdi (lighthouse/olch.mjs) — g'oya bir xil.

Tekshirib ko'ring: Byudjet "birinchi yuklash JS" ga qo'yilgan. Nega "jami JS" ga emas?

Javob

Jami JS ga kech yuklanadigan hamma chunk kiradi: Markdown (46 KB), Tartiblash (15 KB) va boshqalar. Ularni foydalanuvchining bir qismi va keyinroq oladi. Birinchi yuklash esa har foydalanuvchi, har ochilishda to'laydigan narx — LCP va INP'ga to'g'ridan ta'sir qiladi. Jami JS ham kuzatiladi (keshdan tashqari trafik), lekin byudjetning asosiysi — birinchi yuklash.

3. Sahifalarni kech yuklash

3.1 Nima uchun shu yerda eng katta yutuq

lazy darsida komponentni kech yukladik. Marshrut — eng tabiiy chegara: foydalanuvchi bir vaqtda bitta sahifada. vazifalar da birinchi ochiladigan sahifa — ro'yxat. Statistika, sozlamalar, vazifa tahriri va "Qadamlarga bo'lib" formasi — faqat havola bosilganda.

Eng muhimi — ular bilan bog'liqliklar ham ketadi. Tahrir sahifasi React Hook Form va Zod ishlatadi (RHF + Zod). Sahifa birinchi yuklashdan chiqsa, zod (14.6 KB) ham alohida chunk bo'ladi — holat 31 jadvalida u yulduzchasiz.

3.2 React Router lazy

React Router'ning data mode'ida marshrutga element o'rniga lazy beriladi: u Promise qaytaradi, Promise esa { Component } bilan bajariladi. Router sahifa kerak bo'lganda chaqiradi va natijani eslab qoladi.

Natija oynasida dinamik import() yo'q, shuning uchun uni setTimeout li Promise bilan taqlid qilamiz — router uchun farqi yo'q. Statistikaga o'ting, ro'yxatga qayting va yana statistikaga o'ting:

jsx
import {
  createMemoryRouter,
  Link,
  Outlet,
  RouterProvider,
  useNavigation,
} from "react-router";

// Taqlid: import("./StatistikaSahifasi.tsx") — 300 ms tarmoq
function statistikaniYukla() {
  console.log("chunk so'raldi: StatistikaSahifasi");
  return new Promise((hal) =>
    setTimeout(() => hal({ Component: Statistika }), 300),
  );
}

function Statistika() {
  return <h2>Statistika: 3 ta vazifa, 1 tasi bajarildi</h2>;
}

function Qobiq() {
  const nav = useNavigation();
  return (
    <>
      <nav>
        <Link to="/">Ro'yxat</Link>{" "}
        <Link to="/statistika">Statistika</Link>
      </nav>
      {nav.state === "loading" && <p role="status">Yuklanmoqda…</p>}
      <Outlet />
    </>
  );
}

const router = createMemoryRouter([
  {
    path: "/",
    element: <Qobiq />,
    children: [
      { index: true, element: <h2>Ro'yxat</h2> },
      { path: "statistika", lazy: statistikaniYukla },
    ],
  },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

Konsolda:

text
chunk so'raldi: StatistikaSahifasi

Ekranda:

text
Ro'yxat Statistika
Statistika: 3 ta vazifa, 1 tasi bajarildi

Uch kuzatish:

  1. Bir marta. Statistikaga ikki marta o'tildi, chunk bitta so'raldi — router lazy natijasini eslab qoladi. Brauzerda ham xuddi shunday: ikkinchi marta fayl HTTP keshidan keladi.
  2. Oraliq holat. Birinchi o'tishda 300 ms davomida ekranda «Yuklanmoqda…» edi (tekshiruvchi oraliq nuqtada ko'rdi). Bu kech yuklashning narxi.
  3. Ro'yxat sinxron. Bosh sahifa element bilan — u birinchi kadrda chiziladi. Uni ham lazy qilsak, birinchi ochilishda sahifa bo'sh turadi va CLS paydo bo'lishi mumkin.

(Natija oynasida manzil satri yo'q, shuning uchun xotiradagi router; loyihada — createBrowserRouter.)

3.3 Kanon: sahifa() yordamchisi

Holat 31 da marshrutlar.tsx ga kichik yordamchi qo'shildi — har sahifa uchun bir xil qolip (kanon; izoh telefon uchun qayta bo'lindi):

tsx
// 21/#31: ro'yxatdan boshqa sahifalar — kech yuklanadi (router
// lazy): birinchi yuklashda faqat ro'yxat sahifasi (u birinchi
// kadrda chiziladi — CLS 0). Sahifa kodi menyudagi havolaga
// o'tilganda keladi (keyin keshda)
const sahifa =
  <T,>(yuklovchi: () => Promise<T>, nom: keyof T) =>
  () =>
    yuklovchi().then((m) => ({
      Component: m[nom] as React.ComponentType,
    }));

<T,> — .tsx faylida generic arrow funksiya: vergul busiz <T> JSX tegi deb o'qilardi (generics). nom: keyof T — moduldan eksport nomi; noto'g'ri yozsangiz, TypeScript xato beradi. Marshrut esa:

tsx
{
  path: "statistika",
  lazy: sahifa(
    () => import("./sahifalar/StatistikaSahifasi.tsx"),
    "StatistikaSahifasi",
  ),
  handle: nomi("Statistika"),
},

Beshta sahifa shunday o'zgardi: statistika, "Qadamlarga bo'lib", sozlamalar, vazifa umumiy ko'rinishi va tahrir. import lar fayl boshidan olib tashlandi — aks holda statik import ularni baribir asosiy chunk'ga qo'shardi.

3.4 Kechikishni yashirish: oldindan yuklash

TEXNIK-QARZ 68 bu narxni yozib qo'ygan: to'g'ridan ochilgan /statistika avval «Yuklanmoqda…» ko'rsatadi. Keyingi qadam — havolaga sichqoncha kelganda yoki fokusda chunk'ni oldindan so'rash. Foydalanuvchi bosguncha 100–300 ms o'tadi — chunk ko'pincha ulguradi. React Router'ning framework mode'ida bu <Link prefetch="intent">; data mode'da o'zingiz onMouseEnter/onFocus da yuklovchini chaqirasiz (3-mashq).

4. Resurs maslahatlari

Resurs maslahatlari darsida to'rtta <link> ni ko'rgandik. React ilovasidagi o'rni:

Maslahat Nima qiladi vazifalar da
modulepreload JS modulni va uning importlarini oldindan yuklaydi Vite o'zi qo'shadi (asosiy chunk bog'liqliklari)
preload shu sahifa uchun aniq kerak resurs (shrift, LCP rasm) yo'q — web shrift ham, katta rasm ham yo'q
prefetch keyingi sahifa uchun, bo'sh paytda yo'q (TEXNIK-QARZ 68 — hover'da yuklash taklifi)
preconnect boshqa hostga ulanishni (DNS, TLS) oldindan ochadi API — ilmhamroh.uz: birinchi so'rov tezlashardi, o'lchanmagan

Vite va kech chunk. Dinamik import() uchun Vite build'da yordamchi qo'shadi: chunk so'ralganda uning bog'liqliklarini (masalan zod) parallel yuklaydi — "sharshara" (waterfall) bo'lmasin. StatistikaSahifasi yuklansa, u ishlatadigan umumiy chunk ham shu zahoti so'raladi.

React 19. Hujjat metadata va resurs API'lari darsida preload, preconnect, prefetchDNS funksiyalarini ko'rgandik (react-dom dan). Ular komponent ichidan chaqiriladi va React <head> ga bitta <link> qo'yadi (takrorlarsiz). Masalan, vazifa sahifasi ochilganda uning rasmini oldindan:

jsx
import { preload } from "react-dom";

function VazifaSahifasi({ rasm }) {
  preload(rasm, { as: "image", fetchPriority: "high" });
  // …
}

Ehtiyot: har maslahat — tarmoq uchun bellashuv. Hamma narsani preload qilsangiz, haqiqatan muhim narsa sekinlashadi. Qoida: preload — faqat shu sahifaning LCP'siga kerak narsa, ko'pi bilan 2–3 ta.

5. Shriftlar

5.1 vazifalar qarori: tizim shrifti — 0 KB

asos.css dagi body qoidasi (kanon, shriftga oid qismi):

css
body {
  margin: 0;
  font: 1rem/1.5 system-ui, sans-serif;
  /* … ranglar */
}

system-ui — qurilmaning o'z shrifti: Android'da Roboto, iPhone'da San Francisco, Windows'da Segoe UI. Yuklanadigan fayl yo'q — shuning uchun kanon "shrift — tizim steki, 0 KB, o'lchov kerak emas" deb yozdi. Matn birinchi kadrda chiziladi, shrift almashib sakramaydi.

5.2 Brend shrift kerak bo'lsa

«Bahor» brendi o'z shriftini talab qilsa, Web shriftlar darsidagi qoidalar React ilovasida ham o'sha. Qisqa ro'yxat:

  1. Format — faqat WOFF2.
  2. font-display: swap — shrift kelguncha matn tizim shriftida; keyin almashadi. Almashish sakrashini kamaytirish — size-adjust bilan mos zaxira shrift.
  3. Subset — faqat kerakli harflar. O'zbek lotin uchun lotin to'plami yetadi; kirill interfeysi bo'lsa — kirill ham. unicode-range bilan brauzer faqat sahifada uchragan harflar faylini yuklaydi.
  4. O'z serveringizdan. Google Fonts'ni CDN'dan ulash — tashqi host (CSP'ga font-src, style-src va maxfiylik narxi — uchinchi tomon darsidagidek).
  5. preload — faqat birinchi ekrandagi asosiy shrift uchun (crossorigin majburiy).

React/Vite loyihasida o'z serverdan berishning eng oson yo'li — @fontsource paketlari: shrift npm'dan o'rnatiladi va CSS import bilan ulanadi; Vite fayllarni dist/assets ga xesh bilan nusxalaydi. Hujjat bo'yicha:

js
// main.tsx — npm i @fontsource-variable/inter
import "@fontsource-variable/inter/latin.css"; // faqat lotin subset
css
body {
  font-family: "Inter Variable", system-ui, sans-serif;
}

Paket @font-face qoidalarini font-display: swap va unicode-range bilan o'zi beradi. Kanon buni o'lchamadi — vazifalar da web shrift yo'q. Narxini o'zingiz ko'rasiz: Network panelida .woff2 fayllar va LCP o'zgarishi.

6. Server: siqish, HTTP versiyasi va kesh

6.1 Brotli

tekshiruv-31.txt dagi uchinchi ustun — brotli. Birinchi yuklash: gzip 206.6 KB, brotli 177.4 KB — taxminan 14% kam. Brotli — Google yaratgan siqish usuli: matnli fayllar (JS, CSS, HTML) uchun gzip'dan yaxshiroq. Hozirgi brauzerlar uni HTTPS ulanishda Accept-Encoding: br bilan so'raydi.

Birinchi yuklash JS: siqish usuli (holat 31)
  • xom633,8 KB
  • gzip -9206,6 KB
  • brotligzip'dan ~14% kam177,4 KB

Manba: O'lchandi: kanon tekshiruv-31.txt (JAMI birinchi js), 2026-10-17

Brotli'ni frontend kodi emas, server yoqadi: Vercel va Netlify — o'zi; nginx'da — modul bilan. Uni deploy darslarida sozlaymiz. Frontend dasturchining vazifasi — server haqiqatan brotli berayotganini tekshirish: DevTools → Network → fayl → content-encoding: br.

6.2 HTTP/2 va HTTP/3

HTTP versiyalari darsini eslang: HTTP/1.1 da bitta ulanishda bir vaqtda bitta so'rov. Shuning uchun eski maslahat — "fayllarni bitta katta faylga birlashtiring". HTTP/2 bitta ulanishda ko'p so'rovni parallel yuboradi; HTTP/3 esa QUIC ustida — yo'qolgan paket boshqa so'rovlarni to'xtatmaydi (mobil internetda foydali).

Amaliy xulosa: HTTP/2–3 da o'nlab kichik chunk — muammo emas. Holat 31 da 31 ta fayl — normal. Lekin bu "cheksiz bo'lish" degani ham emas: har chunk — alohida siqiladi va umumiy kod takrorlanishi mumkin (TEXNIK-QARZ 56: "fayllararo siqilmaydi").

6.3 Kesh sarlavhalari

Eng tez so'rov — umuman yuborilmagan so'rov. vazifalar ning vercel.json ida (kanon):

json
{
  "source": "/assets/((?!js/|ikonlar/).*)",
  "headers": [
    {
      "key": "Cache-Control",
      "value": "public, max-age=31536000, immutable"
    }
  ]
}

Xeshli fayllar (index-CKFkeGF8.js) bir yilga va immutable — brauzer ularni qayta tekshirmaydi ham. Bu xavfsiz: fayl o'zgarsa, nomi o'zgaradi (hash bilan keshlash). sw.js esa no-cache — yangi versiyani darhol topish uchun (PWA darsidagi yangilanish oqimi shunga tayanadi).

7. Natija: Lighthouse

Kanon Lighthouse 13.5.0 ni mobil sozlamada uch versiyaga bitta sessiyada ishlatdi. Raqamlar mashina holatiga bog'liq, shuning uchun solishtirish faqat bir sessiya ichida. kirgan — tizimga kirgan foydalanuvchi (sessiya oldindan yozilgan, ro'yxat sahifasi ochiladi), yangi — toza profil, birinchi tashrif (kirish oynasi) (lighthouse-31.txt):

Versiya Performance (kirgan / yangi) FCP / LCP (yangi) TBT (yangi)
v10 89 / 93 2.3 s / 2.8 s 90 ms
holat 29 86 / 89 2.6 s / 3.1 s 140 ms
holat 31 88 / 92 2.4 s / 2.8 s 100 ms

Accessibility, Best Practices, SEO — uchalasida ham 100. CLS — 0.

E'tibor bering: 20-qism yakunida xuddi shu v10 uchun Lighthouse 93 / 94 chiqqan edi, bu yerda esa 89 / 93. Kod bir xil — sessiya boshqa (boshqa kun, boshqa mashina yuki). Shuning uchun 21-qism raqamlari faqat shu jadval ichida solishtiriladi, 20-qism raqami bilan emas.

Yana bir raqam haqida: keyingi darsda (holat 32) birinchi yuklash 206.7 KB bo'ladi — SEO uchun <link> teglarini qo'shadigan kod +0.1 KB qo'shadi. Bu darsdagi 206.6 — holat 31 ning o'z o'lchovi.

Halol o'qish: holat 31 v10 ga deyarli qaytdi, lekin to'liq emas (92 vs 93). Sabab — i18n'ning 17 KB i hali ham birinchi yuklashda (ongli qabul qilingan narx). Va Lighthouse ballari bir sessiyada ham 1–2 ball tebranadi — shuning uchun "88 vs 89" ga emas, yo'nalishga qarang: 29 → 31 hamma ko'rsatkich yaxshilandi.

TEXNIK-QARZ 56 (Radix menyu — birinchi yuklashdagi eng katta kutubxona, 20-qism) qayta o'lchanmadi: menyu birinchi sahifada kerak. Uni almashtirish varianti 20-qism tajribasida bor — keyingi qaror.

Tekshirib ko'ring: Nega kanon Lighthouse'ni uch versiyaga bitta sessiyada ishlatdi, v10 ning 20-qismdagi eski natijasini olmadi?

Javob

Lighthouse simulyatsiya qilsa ham, natija kompyuterning o'sha paytdagi yukiga, Chrome versiyasiga va fon jarayonlariga bog'liq. Bir hafta oldingi 93 bilan bugungi 92 ni solishtirish — ikki xil sharoitni solishtirish. Bitta sessiyada uchalasi bir xil sharoitda o'lchanadi — farq kod farqi bo'ladi. Shuning uchun kanon v10 ni ham qayta o'lchadi (89/93).

8. Vazifalar qadami: sahifalar kech yuklanadi (holat 31)

bash
git switch -c perf/sahifalar-lazy

O'zgarish bitta faylda — src/marshrutlar.tsx:

diff
-import { SozlamalarSahifasi } from "./sahifalar/SozlamalarSahifasi.tsx";
-import { StatistikaSahifasi } from "./sahifalar/StatistikaSahifasi.tsx";
 …
-import { VazifaTahriri } from "./sahifalar/VazifaTahriri.tsx";
-import { YangiLoyiha } from "./sahifalar/YangiLoyiha.tsx";
-import { VazifaUmumiy } from "./sahifalar/VazifaUmumiy.tsx";
 …
-            element: <StatistikaSahifasi />,
+            lazy: sahifa(
+              () => import("./sahifalar/StatistikaSahifasi.tsx"),
+              "StatistikaSahifasi",
+            ),

Va «Kanon: sahifa() yordamchisi» bo'limidagi funksiya. Qolgan to'rt sahifa — xuddi shunday. Ro'yxat, kirish, vazifa sahifasining o'zi (loader bilan) va 404 — sinxron qoldi.

Tekshiruvlar (haqiqiy, holat 31):

  • npm test — node:test 255, Vitest 65 (o'zgarmadi); lint, format:check, tip, knip — exit 0.
  • Build: birinchi yuklash JS 225.2 → 206.6 KB gzip (−18.6), CSS 5.1. Yangi chunk'lar: zod 14.6, YangiLoyiha 2.8, VazifaTahriri 1.3, VazifaUmumiy 1.2, SozlamalarSahifasi 1.1, StatistikaSahifasi 0.6 KB. sw.js qobig'i — 39 fayl (29 edi: chunk'lar ko'paydi, hammasi oflayn uchun oldindan keshlanadi).
  • Brauzer: 159 (qayta — tarmoq), e2e-18 72, e2e-19 100 (qayta), e2e-20 77, e2e-21 76.
  • Lighthouse — «Natija» bo'limidagi jadval.

e2e-19 ga 5 ta o'zgarish kerak bo'ldi — kech sahifa endi bir lahza keyin chiqadi: test sarlavha yoki element paydo bo'lishini kutadi, darhol qidirmaydi. Bu kod xatosi emas — xulq ataylab o'zgardi. Yana bir kuzatish: SW faollashuvi navigatsiya paytiga to'g'ri kelsa, sahifa shu ochilishda boshqarilmaydi — normal xulq; test 3 s da boshqaruv bo'lmasa bir marta qayta yuklaydi.

bash
git add -A
git commit

Commit (kanon): perf: ro'yxatdan boshqa sahifalar kech yuklanadi (router lazy) — −18.6 KB.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
lazy qildim, hajm o'zgarmadi o'sha modul boshqa joyda statik import qilingan hamma statik importni olib tashlang, build jadvalini tekshiring
bosh sahifa bo'sh turib, keyin sakraydi birinchi sahifa ham lazy birinchi ekran — sinxron
har o'tishda «Yuklanmoqda…» miltillaydi qisqa kechikishda ham ko'rsatkich hover'da oldindan yuklash yoki kechiktirilgan ko'rsatkich
preload qildim — LCP yomonlashdi ortiqcha preload asosiy resurs bilan bellashadi faqat LCP uchun, 2–3 ta
shrift ikki marta yuklanadi preload da crossorigin yo'q crossorigin qo'shing
Lighthouse har safar boshqa turli sessiya, fon yuk bir sessiyada, bir necha marta

10. Mashqlar

Mashqlar kurs/mashqlar/21/31-yuklanish/ da.

1-mashq (oson): qaysi maslahat?

To'ldiring (preload, prefetch, preconnect, modulepreload):

  • Keyingi sahifaning JS'i, bo'sh paytda:
  • Shu sahifaning LCP rasmi:
  • To'lov API hostiga ulanishni oldindan ochish:
  • Asosiy JS modulning importlari:
Yechim
  • prefetch — past ustuvorlik, keyingi navigatsiya uchun.
  • preload — hozirgi sahifa, yuqori ustuvorlik.
  • preconnect — DNS + TCP + TLS oldindan; resursning o'zi emas.
  • modulepreload — JS modul va uning bog'liqliklari; Vite o'zi qo'yadi.

2-mashq (o'rta): eng katta chunk'lar

Funksiya engKatta(fayllar, n) yozing: [nom, gzip] juftliklaridan gzip bo'yicha eng katta n tasini qaytaradi va har biri uchun KB (bir kasr) va jami ulushini (butun foiz) chiqaradi. Holat 31 ning kech chunk'lari bilan tekshiring:

js
const fayllar = [
  ["IzohKorinishi", 46096],
  ["TartiblashRejimi", 15423],
  ["zod", 14597],
  ["harakat-imkoniyatlari", 14489],
  ["BatafsilStatistika", 14027],
];
engKatta(fayllar, 2);
// IzohKorinishi: 46.1 KB (44%)
// TartiblashRejimi: 15.4 KB (15%)

Ishora: avval nusxa oling (toSorted — asl massiv o'zgarmaydi, massiv metodlari), ulush — har bir faylning jamiga nisbati.

Yechim
js
function engKatta(fayllar, n) {
  const jami = fayllar.reduce((s, [, gz]) => s + gz, 0);
  const tartib = fayllar.toSorted((a, b) => b[1] - a[1]);
  for (const [nom, gz] of tartib.slice(0, n)) {
    const kb = (gz / 1000).toFixed(1);
    const foiz = Math.round((gz / jami) * 100);
    console.log(`${nom}: ${kb} KB (${foiz}%)`);
  }
}

const fayllar = [
  ["IzohKorinishi", 46096],
  ["TartiblashRejimi", 15423],
  ["zod", 14597],
  ["harakat-imkoniyatlari", 14489],
  ["BatafsilStatistika", 14027],
];
engKatta(fayllar, 2);

Konsolda:

text
IzohKorinishi: 46.1 KB (44%)
TartiblashRejimi: 15.4 KB (15%)

Markdown chizuvchi (react-markdown) — kech chunk'larning deyarli yarmi. U faqat izohi bor vazifada yuklanadi, shuning uchun birinchi yuklash byudjetiga tegmaydi. KB — 1000 baytdan (kanon jadvali kabi).

3-mashq (qiyin): hover'da oldindan yuklash

«React Router lazy» misoliga TEXNIK-QARZ 68 dagi taklifni qo'shing: «Statistika» havolasiga sichqoncha kelganda yoki fokus tushganda chunk oldindan so'ralsin. Bosilganda «Yuklanmoqda…» umuman chiqmasin (chunk allaqachon kelgan).

Talablar: yuklovchi bir marta chaqirilsin (Promise eslab qolinadi); router lazy ham o'sha Promise'ni olsin.

Ishora: Promise'ni modul darajasidagi o'zgaruvchida saqlang (let vada = null). onMouseEnter va onFocus — ikkalasi ham (Bobur sichqonchasiz). Klaviatura bilan sinaymiz: Tab ikki marta — fokus «Statistika» da.

Yechim
jsx
import {
  createMemoryRouter,
  Link,
  Outlet,
  RouterProvider,
  useNavigation,
} from "react-router";

function Statistika() {
  return <h2>Statistika: tayyor</h2>;
}

let vada = null;
function statistikaniYukla() {
  if (vada === null) {
    console.log("chunk so'raldi");
    vada = new Promise((hal) =>
      setTimeout(() => hal({ Component: Statistika }), 300),
    );
  }
  return vada;
}

function Qobiq() {
  const nav = useNavigation();
  if (nav.state === "loading") console.log("Yuklanmoqda… ko'rindi");
  return (
    <>
      <nav>
        <Link to="/">Ro'yxat</Link>{" "}
        <Link
          to="/statistika"
          onMouseEnter={statistikaniYukla}
          onFocus={statistikaniYukla}
        >
          Statistika
        </Link>
      </nav>
      {nav.state === "loading" && <p role="status">Yuklanmoqda…</p>}
      <Outlet />
    </>
  );
}

const router = createMemoryRouter([
  {
    path: "/",
    element: <Qobiq />,
    children: [
      { index: true, element: <h2>Ro'yxat</h2> },
      { path: "statistika", lazy: statistikaniYukla },
    ],
  },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

Konsolda:

text
chunk so'raldi

Ekranda:

text
Ro'yxat Statistika
Statistika: tayyor

Fokus «Statistika» ga tushishi bilan chunk so'raldi; 500 ms kutdik (foydalanuvchi o'ylab turgan vaqt) va Enter. Konsolda «Yuklanmoqda… ko'rindi» yo'q — navigatsiya darhol bo'ldi. lazy va onFocus bitta vada ni ishlatadi — ikki marta so'ralmaydi. Real ilovada statistikaniYukla ichida import("./sahifalar/StatistikaSahifasi.tsx") bo'ladi; brauzer o'zi ham dinamik importni bir marta yuklaydi.

4-mashq: Vazifalar qadami

perf/sahifalar-lazy branch'ida marshrutlar.tsx ni «Vazifalar qadami» bo'yicha o'zgartiring.

Yechim

Tekshiruv ro'yxati:

  • npm run build — jadvalda StatistikaSahifasi-*.js, VazifaTahriri-*.js, zod-*.js alohida va yulduzchasiz; birinchi yuklash ~206.6 KB.
  • npm test — node 255, Vitest 65.
  • npm run preview → DevTools → Network → JS filtri. Bosh sahifada statistika chunk'i yo'q. «Statistika» ni bosing — StatistikaSahifasi-*.js paydo bo'ladi.
  • /statistika ni to'g'ridan oching — bir lahza «Yuklanmoqda…».
  • Lighthouse (mobil, inkognito, 3 marta) — Performance 88–92 atrofida, CLS 0.

11. Real ishda

  • "Sayt sekin" shikoyati — eng ko'p uchraydigan vazifalardan biri. Birinchi qadam doim o'lchov: build jadvali, Network panel, Lighthouse. Taxmin bilan optimallashtirish — vaqt isrofi.
  • Byudjet CI'da — katta jamoalarda har pull request'da Lighthouse CI yoki bundle hajmi tekshiriladi. Kod ko'rigida: "bu kutubxona birinchi yuklashga tushyaptimi?".
  • Intervyu savollari: "code splitting qayerda boshlaysiz?", "preload va prefetch farqi?", "font-display: swap nima qiladi?", "brotli va gzip?", "HTTP/2 da fayllarni birlashtirish kerakmi?".

Xulosa

  • O'lchang: gzip — yuklash narxi, xom — bajarish narxi. Byudjet (≤ 210 KB) va har build'da skript yoki Lighthouse CI.
  • Eng katta yutuq — marshrut bo'yicha lazy: sahifa bilan uning kutubxonalari ham ketadi (zod). Birinchi ekran — sinxron.
  • Narx — bir lahzalik «Yuklanmoqda…»; davo — hover/fokusda oldindan yuklash.
  • Maslahatlar: preload — faqat LCP uchun; prefetch — keyingi sahifa; preconnect — tashqi host.
  • Shrift: tizim steki — 0 KB; brend shrift — WOFF2, swap, subset, o'z serverdan (@fontsource).
  • Server: brotli (~14% gzip'dan kam), HTTP/2–3, immutable kesh. Kanon: 225.2 → 206.6 KB, Lighthouse 86/89 → 88/92.

Keyingi dars: SPA uchun SEO — qidiruv tizimi React sahifasini qanday ko'radi, canonical va hreflang, va qachon SSR/SSG'ga o'tish kerak. 21-qismning yakuniy darsi.

Manbalar

  • web.dev: "Performance budgets 101", "Route-based code splitting", "Preload critical assets", "Best practices for fonts"
  • React Router: Route lazy (data mode), useNavigation
  • React 19: react-dom — preload, preconnect, prefetchDNS
  • Lighthouse CI: lighthouserc — assert.assertions, collect.staticDistDir
  • Fontsource: fontsource.org — variable va subset importlari
  • MDN: Cache-Control (immutable), Content-Encoding: br, <link rel="modulepreload">
  • Kanon: vazifalar holat 31, tekshiruv-29.txt, tekshiruv-31.txt, lighthouse-31.txt, vercel.json (2026-10-17)
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
React ilova yuklanishini optimallashtirish: JS byudjeti, sahifalarni kech yuklash, shriftlar va resurslar — IlmHamroh