Mundarija (35)
- Bu darsda
- 1. Nega bu kerak?
- 2. Ikki xil routing
- 2.1 Server routing: har bosish — yangi sahifa
- 2.2 Client routing: sahifa joyida, manzil o'zgaradi
- 3. Router'ning uch vazifasi
- 3.1 Natija oynasida manzil satri yo'q
- 3.2 Uch vazifa — bitta komponentda
- 3.3 Nega <a href>, tugma emas?
- 3.4 Loyihada: haqiqiy pushState
- 4. Sahifa yangilansa: endi server ishga tushadi
- 4.1 Chuqur havola
- 4.2 Zaxira turli hostinglarda
- 4.3 GitHub Pages: 404.html hiylasi va uning narxi
- 5. Nisbiy yo'l tuzog'i
- 5.1 Brauzer yo'lni qanday hisoblaydi
- 5.2 O'lchov: v8 + 404.html
- 6. Hash routing
- 6.1 # dan keyingi qism serverga bormaydi
- 6.2 Nega kanon hash'ni tanlamadi
- 7. Service Worker: oflayn chuqur havola
- 8. Ko'p uchraydigan xatolar
- 9. Vazifalar qadami: chuqur havolaga tayyorgarlik
- 9.1 Branch va o'lchov
- 9.2 Absolyut yo'llar: base — BASE_PATH dan
- 9.3 404.html — build'ning o'zi oladi
- 9.4 Service Worker: har sahifa — index.html
- 10. Mashqlar
- 1-mashq (oson): serverga boradimi?
- 2-mashq (o'rta): «Oldinga» tugmasi
- 3-mashq (qiyin): qaysi bosishni router ushlaydi?
- 4-mashq: vazifalar ga nazar
- 11. Real ishda
- Xulosa
- Manbalar
SPA routing qanday ishlaydi: History API, server zaxirasi va hash routing
Qisqacha: Bitta sahifali ilovada (SPA) havola bosilganda sahifa serverdan qayta yuklanmaydi. Router
history.pushStatebilan manzilni o'zgartiradi va kerakli sahifa komponentini o'zi chizadi, «Orqaga» bosilsa —popstatehodisasini tinglaydi. Lekin sahifa yangilansa (F5) yoki havola tashqaridan ochilsa, so'rov serverga boradi. Server har noma'lum yo'lgaindex.htmlni qaytarishi kerak — bu SPA zaxirasi (SPA fallback). Ilova fayllarining yo'llari esa absolyut bo'lishi shart.
Bu darsda
- Server routing va client routing farqini bitta so'rovlar sxemasida ko'rasiz.
- Router'ning uch vazifasini (manzil → sahifa, havola, «Orqaga») React'da o'zingiz yozasiz.
- Sahifa yangilanganda nima bo'lishini va SPA zaxirasini turli hostinglarda sozlay olasiz.
- Nisbiy yo'l (
./assets/) chuqur havolada nega buzilishininew URLbilan isbotlaysiz. - Hash routing va History API'ni tanlash mezonlarini bilasiz;
vazifalar19-qismga tayyorlanadi.
Oldin bilishingiz kerak: location va History API: oddiy SPA router, State'ni ko'tarish, useSyncExternalStore, React'da auth holati.
1. Nega bu kerak?
18-qism auth darsi bilan tugadi: vazifalar v8 — kirish formasi va bitta katta sahifa. Ro'yxat, filtrlar, statistika paneli, tema tanlovi — hammasi bir joyda. Telefonda bu uzun lenta bo'lib qoldi.
Jasur aka ikki narsa so'radi. Birinchisi: statistika alohida sahifada bo'lsin. Ikkinchisi: Sardorga Telegram'da havola yuborsa, u to'g'ridan-to'g'ri statistikani ochsin — …/vazifalar/statistika. Buning uchun ilova sahifalarga bo'linadi va har sahifaning o'z manzili bo'ladi.
History API darsida siz pushState va popstate bilan oddiy router yozgansiz. useSyncExternalStore darsida URL'dagi ?filtr= ni React'ga ulagansiz. Bu dars ikkalasini bitta rasmga yig'adi. Keyingi darsdan boshlab tayyor kutubxona — React Router — bilan ishlaymiz. Uni to'g'ri ishlatish uchun esa uning ichida va server tomonida nima bo'lishini bilish kerak.
Hayotiy o'xshatish: teatr. Oddiy sayt — har sahna uchun tomoshabinni boshqa binoga ko'chirish: kiyinish, yo'l, yangi o'rindiq. SPA — bitta zal. Parda yopiladi, dekoratsiya almashadi, tomoshabin joyidan turmaydi. Lekin kimdir "3-sahnaga chipta" bilan to'g'ridan-to'g'ri kelsa, kassa uni baribir shu zalga yo'llashi kerak. Bu dars ana shu kassa haqida ham.
2. Ikki xil routing
2.1 Server routing: har bosish — yangi sahifa
Routing (yo'naltirish) — manzilga qarab qaysi sahifani ko'rsatishni hal qilish. HTML darslaridagi oddiy saytda buni server qiladi. Siz menyu.html havolasini bosasiz. Brauzer serverdan menyu.html ni so'raydi, eski sahifani butunlay tashlaydi va yangisini noldan quradi. Bu server routing (server tomonida yo'naltirish).
Bunda JavaScript holati har o'tishda yo'qoladi. Formaga yozilgan matn, ochiq menyu, xotiradagi ro'yxat — hammasi. CSS va JS fayllari keshdan bo'lsa ham qayta bajariladi, ekran bir lahza oqarib ketadi.
2.2 Client routing: sahifa joyida, manzil o'zgaradi
Client routing (mijoz tomonida yo'naltirish) — yo'naltirishni brauzerdagi JavaScript qiladi. Server faqat bir marta, boshida, index.html va JS faylini beradi. Keyin havola bosilganda:
- JS standart o'tishni to'xtatadi (
preventDefault); - manzil satrini
history.pushStatebilan o'zgartiradi — so'rov yo'q; - yangi manzilga mos komponentni chizadi.
Shunday ishlaydigan ilova — SPA (Single Page Application, bitta sahifali ilova). Bu kodni yozadigan qism — router. Manzil va unga mos sahifa juftligi esa marshrut (route) deyiladi: /statistika → StatistikaSahifasi.
Ikki yo'lni yonma-yon ko'ring. Yuqorida — har bosishda server, pastda — server faqat birinchi marta:
sequenceDiagram
participant B as Brauzer
participant R as Router (JS)
participant S as Server
Note over B,S: Server routing
B->>S: GET /statistika.html
S-->>B: yangi HTML, sahifa noldan
Note over B,S: Client routing
B->>S: GET / (faqat birinchi marta)
S-->>B: index.html + JS
B->>R: havola bosildi
R->>B: pushState("/statistika")
R->>B: Statistika komponentini chizDiagrammaning pastki qismida R dan keyin S ga o'q yo'q. Sahifa almashdi, server esa bundan bexabar.
| Server routing | Client routing (SPA) | |
|---|---|---|
| Kim sahifa tanlaydi | server | brauzerdagi router |
| O'tishda tarmoq | yangi HTML | ko'pincha hech narsa |
| JS holati | har o'tishda yo'qoladi | saqlanadi |
| Birinchi ochilish | darhol HTML | JS yuklanib, chizilguncha |
Oxirgi qatorga e'tibor bering. SPA'ning ham narxi bor: birinchi ochilishda sahifa bo'sh <div>, mazmunni JS chizadi. Bunga «Real ishda» bo'limida qaytamiz.
Tekshirib ko'ring: SPA'da «Statistika» havolasini bosdingiz, keyin brauzerning «Orqaga» tugmasini. Shu ikki harakatda serverga nechta sahifa so'rovi ketdi?
Javob
Nolta. Havola bosilganda router pushState qildi — bu so'rov emas. «Orqaga» bosilganda brauzer popstate hodisasini chiqardi, router eski sahifani o'zi chizdi. Sahifa ma'lumot so'rashi mumkin (API'dan) — lekin bu HTML sahifa so'rovi emas.
3. Router'ning uch vazifasi
Har qanday router — o'zingiz yozgani ham, React Router ham — uchta ishni bajaradi:
| Vazifa | Brauzerda nima bilan |
|---|---|
| Manzilga qarab sahifa tanlash | location.pathname → komponent |
| Havola bosilganda o'tish | preventDefault + pushState + qayta chizish |
| «Orqaga» va «Oldinga» | popstate hodisasi → qayta chizish |
Keling, uchalasini React'da yozamiz. Lekin avval bitta to'siq.
3.1 Natija oynasida manzil satri yo'q
Saytdagi «Ishga tushir» kodni alohida natija oynasida bajaradi. U — sandbox'li iframe, manzili about:srcdoc. Unda haqiqiy manzil satri yo'q. History API darsida ko'rgansiz: bunday hujjatda pushState SecurityError tashlaydi.
Shuning uchun jonli misolda tarixni xotirada saqlaymiz: manzillar massivi va "hozir qaysi birida turibmiz" raqami. Brauzer tarixi ham ichkarida aynan shunday tuzilgan. React Router'da ham shunday router bor — createMemoryRouter, keyingi darsda natija oynasida shuni ishlatamiz. Loyihada esa haqiqiy history.pushState ishlaydi — kodi shu bo'lim oxirida.
3.2 Uch vazifa — bitta komponentda
«Bahor» saytining uch sahifasi: bosh sahifa, menyu va aloqa. Mehmon «Menyu» ni, keyin «Aloqa» ni bosadi va «Orqaga» qaytadi:
import { useState } from "react";
// Marshrutlar: manzil → sahifa komponenti
const routes = {
"/": () => <h1>Bahor oshxonasi</h1>,
"/menyu": () => <h1>Menyu: osh, lag'mon, manti</h1>,
"/aloqa": () => <h1>Tel: +998 90 000 00 00</h1>,
};
function NotFound() {
return <h1>Sahifa topilmadi</h1>;
}
function Link({ to, onNavigate, children }) {
function handleClick(event) {
// Ctrl/Cmd/Shift + bosish — yangi tab: brauzerning o'zi qilsin
if (event.ctrlKey || event.metaKey || event.shiftKey) return;
event.preventDefault(); // sahifa serverdan yuklanmasin
onNavigate(to);
}
return (
<a href={to} onClick={handleClick}>
{children}
</a>
);
}
function App() {
// Tarix: manzillar ro'yxati va hozir qaysi birida turganimiz
const [memory, setMemory] = useState({
entries: ["/"],
index: 0,
});
const path = memory.entries[memory.index];
function navigate(to) {
// pushState kabi: joriydan keyingilarni tashlab, yangisini qo'sh
setMemory((m) => ({
entries: [...m.entries.slice(0, m.index + 1), to],
index: m.index + 1,
}));
}
function goBack() {
// «Orqaga» kabi: ro'yxat o'sha, faqat ko'rsatkich bitta orqaga
setMemory((m) => ({ ...m, index: Math.max(0, m.index - 1) }));
}
console.log("manzil:", path, "| tarix:", memory.entries.join(" "));
const Page = routes[path] ?? NotFound;
return (
<div>
<nav>
<Link to="/" onNavigate={navigate}>Bosh sahifa</Link>
{" | "}
<Link to="/menyu" onNavigate={navigate}>Menyu</Link>
{" | "}
<Link to="/aloqa" onNavigate={navigate}>Aloqa</Link>
</nav>
<button onClick={goBack}>Orqaga</button>
<Page />
</div>
);
}Konsolda:
manzil: / | tarix: /
manzil: /menyu | tarix: / /menyu
manzil: /aloqa | tarix: / /menyu /aloqa
manzil: /menyu | tarix: / /menyu /aloqaEkranda:
Bosh sahifa | Menyu | Aloqa
Orqaga
Menyu: osh, lag'mon, mantiKonsolni qatorma-qator o'qiymiz:
| Qadam | entries |
index |
Ekranda |
|---|---|---|---|
| boshlanish | / |
0 | Bahor oshxonasi |
| «Menyu» | /, /menyu |
1 | Menyu |
| «Aloqa» | /, /menyu, /aloqa |
2 | Aloqa |
| «Orqaga» | o'sha uchta | 1 | Menyu |
To'rtinchi qatorga qarang: «Orqaga» tarixdan hech narsani o'chirmadi. /aloqa ro'yxatda qoldi — «Oldinga» uchun. Faqat ko'rsatkich siljidi. Endi shu holatda yangi havola bosilsa, slice(0, index + 1) /aloqa ni kesib tashlaydi. Brauzer tarixi ham aynan shunday: orqaga qaytib yangi sahifaga o'tsangiz, «Oldinga» o'chadi.
Uch vazifa kodda qayerda:
- Manzil → sahifa:
routes[path] ?? NotFound. Mos marshrut yo'q bo'lsa — "topilmadi" sahifasi. Bu??(Nullish coalescing) bo'lmasa,/yoqmanzilidaPageundefinedbo'lib, React yiqilardi. - Havola:
Link— oddiy<a href>, lekin bosilgandapreventDefaultvanavigate.navigate—pushStatening o'rnini bosadi. - «Orqaga»:
goBack—popstatening o'rnini bosadi. Haqiqiy brauzerda bu tugma sizning kodingizda emas, brauzerning o'zida.
Manzil — state. U o'zgarsa, React App ni qayta chizadi va yangi sahifa chiqadi. UI = f(state) g'oyasi routing'da ham o'sha: sahifa — manzilning funksiyasi.
React Router'ning createMemoryRouter ichidagi tarix ham xuddi shu ikki maydondan iborat. react-router 8.4.0 manba kodida (createMemoryHistory) push shunday: index += 1; entries.splice(index, entries.length, nextLocation). Biz yozgan slice bilan bir xil ish.
3.3 Nega <a href>, tugma emas?
Link ichida <button> ham ishlardi. Lekin havola — havola bo'lib qolishi kerak:
- Ctrl + bosish yoki sichqonchaning o'rta tugmasi — yangi tabda ochish. Buni
hrefberadi.Linkdagi birinchi tekshiruv aynan shuning uchun: Ctrl bosilgan bo'lsa, biz aralashmaymiz. - O'ng tugma → "Havolani nusxalash" — Sardorga Telegram'da yuborish uchun.
- Malika (ekran o'quvchi) "havola, Menyu" deb eshitadi va havolalar ro'yxatidan topadi.
- JS hali yuklanmagan bo'lsa ham
hrefishlaydi — oddiy sahifa yuklanishi bilan.
React Router'ning <Link> i ham <a href> chizadi va xuddi shu tekshiruvlarni qiladi. Uchinchi mashqda shu tekshiruvni o'zingiz yozasiz.
3.4 Loyihada: haqiqiy pushState
Manzil satri bor haqiqiy sahifada tarixni brauzer saqlaydi. Manzil — React'dan tashqaridagi qiymat, demak unga useSyncExternalStore bilan obuna bo'linadi. Bu kod natija oynasida ishlamaydi (pushState — SecurityError), shuning uchun tugmasiz:
import { useSyncExternalStore } from "react";
const listeners = new Set();
function subscribe(listener) {
listeners.add(listener);
addEventListener("popstate", listener); // «Orqaga» / «Oldinga»
return () => {
listeners.delete(listener);
removeEventListener("popstate", listener);
};
}
export function navigate(to) {
history.pushState(null, "", to);
// pushState hodisa bermaydi — tinglovchilarga o'zimiz aytamiz
for (const listener of listeners) listener();
}
export function usePathname() {
return useSyncExternalStore(subscribe, () => location.pathname);
}Jonli misoldagi memory o'rnida endi usePathname(), goBack esa umuman kerak emas — brauzerning tugmasi popstate beradi. Qolgani o'sha.
react-router (8.4.0) dagi createBrowserRouter ham shu ikki qismga qurilgan. Manba kodida globalHistory.pushState(historyState, "", url) va popstate tinglovchisi bor. Farqi — history.state ga null emas, { usr, key, idx } yozadi. idx — tarixdagi tartib raqami. Router u bilan «Orqaga» necha qadam bo'lganini hisoblaydi. Shu sababli tarixga router'dan tashqarida, o'zingiz pushState(null) yozmang. vazifalar 17-qismdan beri aynan shunday qiladi — buni URL — holat joyi darsida o'lchab tuzatamiz.
Tekshirib ko'ring: Jonli misolda «Menyu» → «Aloqa» → «Orqaga» → «Bosh sahifa» bosildi.
memory.entriesda nima qoladi?
Javob
["/", "/menyu", "/"], index: 2. «Orqaga» dan keyin index 1 edi (/menyu). «Bosh sahifa» slice(0, 2) bilan /aloqa ni kesdi va / ni qo'shdi. «Oldinga» endi hech qayerga olib bormaydi — brauzerda ham shunday.
4. Sahifa yangilansa: endi server ishga tushadi
4.1 Chuqur havola
Client routing faqat sahifa ochiq paytda ishlaydi. Uch holatda brauzer baribir serverga boradi:
- sahifa yangilandi (F5);
- havola Telegram'dan, xatchodan yoki xatdan ochildi;
- manzil satriga qo'lda yozildi.
Ilovaning ichki sahifasiga to'g'ridan-to'g'ri olib boradigan bunday havola — chuqur havola (deep link). Server /vazifalar/statistika so'rovini oladi va diskdan shu nomli faylni qidiradi. Bunday fayl yo'q: diskda faqat index.html va assets/ bor. Server 404 qaytaradi. Router hech qachon ishga tushmaydi, chunki uning JS fayli ham kelmadi.
Yechim: "fayl topilmasa — index.html ni ber". Bu — SPA zaxirasi (SPA fallback). Shunda index.html keladi, JS yuklanadi, router location.pathname ni o'qiydi va statistikani chizadi:
flowchart TD
A["F5: /vazifalar/statistika"] --> B{"Shu nomli fayl bormi?"}
B -->|"ha"| C["Faylni beradi"]
B -->|"yo'q"| D{"Zaxira sozlanganmi?"}
D -->|"yo'q"| E["404: ilova yo'q"]
D -->|"ha"| F["index.html"]
F --> G["JS yuklanadi"]
G --> H["Router manzilni o'qiydi"]
H --> I["Statistika chiziladi"]Diagrammada sahifani tanlaydigan oxirgi qadam yana brauzerda. Server sahifani "tushunmaydi" — u faqat hamma yo'lga bitta faylni beradi.
4.2 Zaxira turli hostinglarda
| Joy | Qanday sozlanadi | HTTP holati |
|---|---|---|
npm run dev, preview (Vite) |
o'zi qiladi | 200 |
| Vercel | vercel.json → rewrites |
200 |
| Netlify | _redirects: /* /index.html 200 |
200 |
| Nginx | try_files $uri /index.html; |
200 |
| GitHub Pages | sozlama yo'q → 404.html hiylasi |
404 |
vazifalar ning vercel.json ida (Vercel deploy darsidan beri) shu qator bor:
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]Rewrite (ichki almashtirish) — server foydalanuvchiga boshqa manzilni ko'rsatmay, ichkarida boshqa faylni beradi. Manzil satrida /statistika qoladi, javob — index.html mazmuni, holati 200. Yo'naltirish (redirect) esa manzilning o'zini o'zgartirardi — bu bizga kerak emas.
Bir nozik joy: Vercel bu qoidani faqat haqiqiy fayl topilmaganda qo'llaydi. /assets/index-….js — mavjud fayl, o'zi beriladi. Aks holda JS so'roviga ham HTML kelardi.
4.3 GitHub Pages: 404.html hiylasi va uning narxi
vazifalar ning asosiy manzili — GitHub Pages (17-qismdan). Pages'da rewrite yo'q. Lekin bitta qoida bor: topilmagan sahifa uchun sayt papkasidagi 404.html ni ko'rsatadi. Agar 404.html — index.html ning aynan nusxasi bo'lsa? Unda topilmagan har manzilda ilovaning o'zi ochiladi va sahifani router tanlaydi.
Bu ishlaydi. Lekin halol bo'laylik: server javobining holati baribir 404. Biz buni o'lchadik — kanon o'lchovi natijalar/02-chuqur-havola-v9.txt, Chrome 155, Pages kabi server (keyingi darsdagi holat):
/vazifalar/ → HTTP 200 | #ilova: 96 element | h2: "Ro'yxat" | title: "(2) Vazifalar — kichik vazifalar ro'yxati"
/vazifalar/statistika → HTTP 404 | #ilova: 32 element | h2: "Statistika" | title: "Statistika — Vazifalar"
konsol: Failed to load resource: the server responded with a status of 404 (Not Found)Sahifa ochildi, sarlavha "Statistika" — foydalanuvchi farqni sezmaydi. Narxi ikkita:
- Konsolda xato:
Failed to load resource: the server responded with a status of 404 (Not Found)— "Resursni yuklab bo'lmadi: server 404 (Topilmadi) holati bilan javob berdi". Brauzer sahifa so'rovining holatini ko'rdi va yozdi. Ilova uchun zararsiz, lekin DevTools'ni ochgan odamni chalg'itadi. - Qidiruv tizimi 404 ni "bu sahifa yo'q" deb tushunadi va indekslamaydi. Kirish talab qiladigan
vazifalaruchun bu muammo emas. Ochiq blog yoki do'kon uchun — muammo. Ular Vercel/Netlify'ni yoki server rendering'ni tanlaydi.
Kanon buni yashirmaydi: TEXNIK-QARZ.md da 48-band. Vercel'da o'sha manzil 200 bilan ochiladi — vercel.json tayyor.
Tekshirib ko'ring: Nega
404.htmlniindex.htmldan qo'lda nusxa qilib, repoga qo'yib qo'yish yomon fikr?
Javob
index.html har build'da o'zgaradi: unga hash'li fayl nomlari yoziladi (index-PzJwsspb.js). Qo'lda olingan nusxa bir build'dan keyin eskiradi va mavjud bo'lmagan JS faylni so'raydi — chuqur havola yana bo'sh ekran beradi. Shuning uchun kanonda nusxani build'ning o'zi oladi («Vazifalar qadami» da).
5. Nisbiy yo'l tuzog'i
5.1 Brauzer yo'lni qanday hisoblaydi
Zaxira bor, index.html keldi. Endi brauzer undagi fayllarni so'raydi. 18-qismgacha vazifalar da yo'llar nisbiy edi: Vite base: "./" bilan ./assets/index-….js deb yozardi. Nega shunday edi? Sayt login.github.io/vazifalar/ papkasida, papka nomi repoga qarab o'zgaradi. Nisbiy yo'l har papkada ishlardi.
Nisbiy yo'l joriy sahifa manziliga nisbatan hisoblanadi. Buni Node'da URL bilan tekshiramiz — brauzer xuddi shu algoritmni ishlatadi (URL va URLSearchParams):
const file = "./assets/index.js";
const site = "https://example.com";
// sahifa: /vazifalar/ (ro'yxat)
const home = new URL(file, site + "/vazifalar/");
console.log(home.pathname); // /vazifalar/assets/index.js
// sahifa: /vazifalar/vazifa/2 (chuqur havola)
const deep = new URL(file, site + "/vazifalar/vazifa/2");
console.log(deep.pathname); // /vazifalar/vazifa/assets/index.jsIkkinchi natija — muammo. /vazifalar/vazifa/2 sahifasi uchun "joriy papka" — /vazifalar/vazifa/. Brauzer JS faylni o'sha yerdan qidiradi. U yerda fayl yo'q — server yana 404.html ni beradi, HTML'ni JS sifatida bajarib bo'lmaydi.
Bitta darajali manzil (/vazifalar/statistika) da esa joriy papka /vazifalar/ — fayllar topiladi. Shuning uchun tuzoq ikki bo'lakli manzilda birinchi marta chiqadi. Bizda bu — dinamik marshrutlar darsidagi /vazifa/2.
5.2 O'lchov: v8 + 404.html
Biz v8 build'iga 404.html nusxasini qo'yib ko'rdik, base: "./" esa o'zgarmadi (natijalar/01-chuqur-havola-v8.txt):
/vazifalar/vazifa/2 → HTTP 404 | #ilova: 0 element | h2: null | title: "Vazifalar — kichik vazifalar ro'yxati"
yuklanmadi (4): 404 /vazifalar/vazifa/assets/js/tema.js, 404 /vazifalar/vazifa/assets/index-BUB1eWti.js, 404 /vazifalar/vazifa/assets/index-QnSPLFv3.css, 404 /vazifalar/vazifa/assets/ikonlar/ikon.svg
/vazifalar/statistika → HTTP 404 | #ilova: 88 element | h2: "Ro'yxat" | title: "(2) Vazifalar — kichik vazifalar ro'yxati"Uch narsani ko'ring:
vazifa/2da to'rtta fayl topilmadi — hammasi/vazifalar/vazifa/assets/...dan so'ralgan.#ilovada 0 element: oq ekran.titlebor — u HTML'ning o'zida yozilgan, JS kerak emas.statistikada fayllar yuklandi, lekin ekranda — "Ro'yxat". Hali router yo'q, v8 har manzilda bitta sahifani chizadi.
Yechim — absolyut yo'l: /vazifalar/assets/index-….js. U qaysi sahifadan so'ralsa ham bir joyga olib boradi. Papka nomi build vaqtida beriladi — «Vazifalar qadami» da.
Maslahat: Chuqur havolani doim ikki bo'lakli manzil bilan sinang (
/vazifa/2,/menyu/osh). Bir bo'lakli manzil nisbiy yo'l xatosini yashiradi.
6. Hash routing
6.1 # dan keyingi qism serverga bormaydi
Zaxirasiz ishlaydigan yo'l ham bor. History API darsida ko'rgansiz: manzilning # dan keyingi qismi serverga yuborilmaydi. …/vazifalar/#/statistika ochilganda server faqat /vazifalar/ ni ko'radi — bu mavjud papka, 200. Sahifani router location.hash dan tanlaydi, o'tishni esa hashchange hodisasidan biladi:
// Hash router'ning yuragi: #/statistika → "/statistika"
function currentPath() {
return location.hash.slice(1) || "/";
}
addEventListener("hashchange", () => render(currentPath()));Bu — hash routing. React Router'da ham bor (createHashRouter). GitHub Pages'da zaxira kerak emas, status — 200.
6.2 Nega kanon hash'ni tanlamadi
History API (/statistika) |
Hash (#/statistika) |
|
|---|---|---|
| Server sozlamasi | zaxira kerak | kerak emas |
| Manzil ko'rinishi | toza | #/ bilan |
# ning o'z vazifasi |
bo'sh — #royxat ishlaydi |
router egallaydi |
| Server sahifa yo'lini ko'radimi | ha (SSR, statistika) | yo'q |
vazifalar uchun hal qiluvchi sabablar ikkita. Birinchisi — mavjud havolalar. History API darsidan beri filtr ?filtr=faol da, odamlar bunday havolalarni saqlab qo'ygan. Hash'ga o'tish ularning hammasini o'zgartirardi. Ikkinchisi — # band. Skip link href="#royxat" (Fokus boshqaruvi) va sahifa ichidagi langarlar # ning odatiy vazifasi.
Narxi — Pages'da 404 holati. Kanon uni qabul qildi va ochiq yozdi. Shunday qarorlarni o'lchov bilan qabul qilish — kursning qoidasi.
Tekshirib ko'ring: Hash routing'da sahifa yangilansa (F5), serverga qaysi manzil boradi:
/vazifalar/#/statistikayoki/vazifalar/?
Javob
/vazifalar/. Brauzer # dan keyingi qismni so'rovga qo'shmaydi. Server mavjud papkani beradi (200), JS yuklangach router location.hash dan /statistika ni o'qiydi. Shuning uchun hash routing'ga zaxira kerak emas.
7. Service Worker: oflayn chuqur havola
vazifalar internetsiz ham ochiladi — 13-qismdagi Service Worker sahifani keshdan beradi. Lekin v8 dagi SW faqat /vazifalar/ yoki /vazifalar/index.html ni "ilova sahifasi" deb bilardi. Oflayn holda /vazifalar/statistika ochilsa, SW unga aralashmasdi — brauzerning "Internet yo'q" sahifasi chiqardi.
SW uchun ham qoida server bilan bir xil: ilovaning har sahifasi — o'sha bitta index.html. Faqat endi "sahifa"ni qanday tanish kerak? Kanondagi javob: kengaytmasi yo'q yo'l — sahifa (/statistika, /vazifa/2); kengaytmalisi (README.md, manifest.webmanifest) — fayl, .html dan tashqari. Kod — «Vazifalar qadami» da.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| F5 dan keyin 404 | serverda SPA zaxirasi yo'q | rewrite yoki 404.html nusxasi |
chuqur havolada oq ekran, Network'da …/vazifa/assets/… 404 |
nisbiy yo'llar (./assets/) |
absolyut base |
JS so'roviga HTML keladi: Unexpected token '<' |
rewrite hamma narsani index.html ga burgan |
faqat topilmagan yo'lga zaxira |
| havola bosilganda sahifa oqarib, qayta yuklanadi | <a href> preventDefault siz |
router'ning Link i |
| «Orqaga» bosilsa manzil o'zgaradi, ekran — yo'q | popstate tinglanmagan |
popstate → qayta chizish |
| Ctrl + bosish yangi tab ochmaydi | hamma bosish ushlangan | modifikator tugmalarni o'tkazib yuborish |
SecurityError: Failed to execute 'pushState' |
boshqa origin'ga yozish yoki natija oynasi | o'z origin'idagi yo'l; natija oynasida — xotiradagi tarix |
Uchinchi qatordagi xabar — SyntaxError: Unexpected token '<' ("kutilmagan belgi <"). Brauzer JS kutgan joyda <!doctype html> ni o'qib, birinchi belgidayoq to'xtaydi. Xabar < haqida, chunki HTML shu belgi bilan boshlanadi — JS kodi emas.
9. Vazifalar qadami: chuqur havolaga tayyorgarlik
9.1 Branch va o'lchov
Bu va keyingi dars — kanonda bitta qadam (holat 02). Bu darsda serverga tegishli qismni tayyorlaymiz, router — keyingi darsda, commit ham o'sha yerda.
git switch -c feat/sahifalarMuammo «O'lchov: v8 + 404.html» bo'limida ko'rindi: Pages'da chuqur havola ochilmaydi (zaxira yo'q), ochilsa ham fayllar topilmaydi (nisbiy yo'l). Uchta tuzatish kerak.
9.2 Absolyut yo'llar: base — BASE_PATH dan
vite.config.ts da (kanondan, aynan):
// 19/#02: yo'llar ABSOLYUT ("/vazifalar/assets/..."). 18-qismgacha
// nisbiy edi ("./") — bitta sahifada ishlardi; /vazifalar/vazifa/2
// dan esa "./assets/" → /vazifalar/vazifa/assets/ (404, o'lchandi:
// natijalar/01-chuqur-havola-v8.txt). Papka nomi — BASE_PATH dan
// (pages.yml: repo nomi), bo'lmasa "/" (Vercel, lokal sinov)
base: env.BASE_PATH || "/",env — loadEnv natijasi (16-qismdagi vite.config.ts). Papka nomi kodga yozilmaydi: GitHub Actions uni repo nomidan oladi. pages.yml ga:
# 19/#02: sayt login.github.io/<repo>/ da — yo'llar absolyut
# (vite.config.ts base), papka nomi repo nomidan
- run: npm run build
env:
BASE_PATH: /${{ github.event.repository.name }}/Vite <script> va <link rel="stylesheet"> yo'llarini o'zi almashtiradi. Lekin index.html dagi boshqa havolalarni (manifest, ikonka, tema.js) qo'lda yozganmiz. Ular uchun Vite'ning %BASE_URL% o'rinbosari bor — build'da base qiymatiga almashadi:
<link rel="manifest" href="%BASE_URL%manifest.webmanifest">
<link rel="icon" href="%BASE_URL%assets/ikonlar/ikon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="%BASE_URL%assets/ikonlar/ikon-180.png">
<script src="%BASE_URL%assets/js/tema.js"></script>JS ichida o'sha qiymat — import.meta.env.BASE_URL. Kanonda u bilan SW manzili ham absolyut bo'ldi (xavfsiz-manzil.ts): nisbiy "sw.js" /vazifalar/vazifa/2 sahifasida /vazifalar/vazifa/sw.js bo'lardi.
9.3 404.html — build'ning o'zi oladi
Vite plagini (Vite plaginlari) build tugagach index.html ni nusxalaydi. writeBundle — Vite fayllarni diskka yozib bo'lgach chaqiradigan bosqich. apply: "build" — faqat build'da, dev serverda emas:
{
// SPA zaxirasi (19/#02): GitHub Pages /vazifalar/vazifa/2 ni
// topolmaydi (bunday fayl yo'q) va 404.html ni beradi — u
// index.html ning nusxasi bo'lsa, ilova ochiladi va sahifani
// router tanlaydi (HTTP status — 404). Vercel'da kerak emas:
// vercel.json "rewrites" index.html ni 200 bilan beradi
name: "spa-zaxira",
apply: "build",
writeBundle(chiqish) {
const papka = chiqish.dir ?? "dist";
copyFileSync(
resolve(papka, "index.html"),
resolve(papka, "404.html"),
);
},
},copyFileSync va resolve — node:fs va node:path dan (16-qismda ko'rgansiz). Nusxa har build'da yangidan olinadi — hash'li nomlar doim to'g'ri. skriptlar/qobiq.js esa 404.html ni SW qobig'iga qo'shmaydi: u faqat Pages serveri uchun.
9.4 Service Worker: har sahifa — index.html
public/sw.js dagi navigatsiya tekshiruvi:
- // Faqat ilova sahifasi: /vazifalar/ yoki /vazifalar/index.html
- const sahifami = /\/(index\.html)?$/.test(url.pathname);
+ const faylmi = /\.[a-z0-9]+$/i.test(url.pathname);
+ const sahifami = !faylmi || url.pathname.endsWith(".html");
return sahifami ? "sahifa" : null;Kanondagi besh qatorli izoh bu yerda qisqartirildi: ilova sahifalarga bo'lingan, hammasi bitta index.html, sahifani router tanlaydi; kengaytmali fayl — sahifa emas. Yangi node testi buni tekshiradi: tarmoq yo'q, so'rov — vazifa/2, javob — kesh: index.html. Testlar: node 173 → 174.
Hozircha ilova o'zgarmadi — router yo'q, hamma manzilda ro'yxat. Lekin endi u istalgan chuqurlikdagi manzildan ochiladi. Keyingi darsda unga sahifalarni o'rgatamiz.
10. Mashqlar
Mashqlar kurs/mashqlar/19/01-spa-routing/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.
1-mashq (oson): serverga boradimi?
SPA zaxirasi sozlangan vazifalar da. Har harakat uchun yozing: serverga sahifa so'rovi ketadimi (ha yoki yo'q)?
- «Statistika» havolasi bosildi:
- Brauzerning «Orqaga» tugmasi bosildi:
- Statistika sahifasida F5 bosildi:
- Sardor Telegram'dagi havolani ochdi:
#/statistikadan#/sozlamalarga o'tildi:
Yechim
Ilova ichidagi o'tishlar — pushState, popstate, hashchange — so'rovsiz. Sahifa yangidan yuklangan har holatda (F5, tashqi havola, manzil satri) so'rov serverga boradi. Aynan shu so'rovlar uchun SPA zaxirasi kerak.
2-mashq (o'rta): «Oldinga» tugmasi
«Uch vazifa — bitta komponentda» bo'limidagi routerga «Oldinga» tugmasini qo'shing. U index ni bitta oldinga siljitsin, lekin entries oxiridan o'tib ketmasin. Sinov: «Menyu», «Aloqa», «Orqaga», «Orqaga», «Oldinga» — ekranda menyu bo'lishi kerak.
Ishora: goBack dagi Math.max(0, …) ning teskarisi — Math.min(…, m.entries.length - 1).
Yechim
import { useState } from "react";
const routes = {
"/": () => <h1>Bahor oshxonasi</h1>,
"/menyu": () => <h1>Menyu: osh, lag'mon, manti</h1>,
"/aloqa": () => <h1>Tel: +998 90 000 00 00</h1>,
};
function Link({ to, onNavigate, children }) {
function handleClick(event) {
if (event.ctrlKey || event.metaKey || event.shiftKey) return;
event.preventDefault();
onNavigate(to);
}
return (
<a href={to} onClick={handleClick}>
{children}
</a>
);
}
function App() {
const [memory, setMemory] = useState({
entries: ["/"],
index: 0,
});
const path = memory.entries[memory.index];
function navigate(to) {
setMemory((m) => ({
entries: [...m.entries.slice(0, m.index + 1), to],
index: m.index + 1,
}));
}
function go(delta) {
// -1 — orqaga, +1 — oldinga; chegaradan chiqmaydi
setMemory((m) => ({
...m,
index: Math.min(
Math.max(0, m.index + delta),
m.entries.length - 1,
),
}));
}
console.log("manzil:", path, "| index:", memory.index);
const Page = routes[path];
return (
<div>
<nav>
<Link to="/menyu" onNavigate={navigate}>Menyu</Link>
{" | "}
<Link to="/aloqa" onNavigate={navigate}>Aloqa</Link>
</nav>
<button onClick={() => go(-1)}>Orqaga</button>
<button onClick={() => go(1)}>Oldinga</button>
<Page />
</div>
);
}Konsolda:
manzil: / | index: 0
manzil: /menyu | index: 1
manzil: /aloqa | index: 2
manzil: /menyu | index: 1
manzil: / | index: 0
manzil: /menyu | index: 1Ekranda:
Menyu | Aloqa
OrqagaOldinga
Menyu: osh, lag'mon, mantiIkki tugma bitta go(delta) funksiyasiga yig'ildi — brauzerdagi history.go(-1) va history.go(1) ham shunday. Math.min(Math.max(…)) — qiymatni 0 … length - 1 oralig'ida ushlab turadi. react-router dagi createMemoryHistory da xuddi shu ish clampIndex funksiyasi bilan qilinadi.
3-mashq (qiyin): qaysi bosishni router ushlaydi?
React Router'ning Link i bosishni ushlashdan oldin tekshiradi. shouldHandleClick(click) funksiyasini yozing. U true qaytarsin, faqat agar:
- chap tugma bosilgan (
button === 0); - Ctrl, Cmd (
metaKey), Shift, Alt bosilmagan; - havolada
target="_blank"yo'q; - havola o'z sayti ichida (
originbir xil).
click — oddiy obyekt: { button, ctrlKey, metaKey, shiftKey, altKey, target, href }. Sayt manzili — https://example.com. Havolaning origin'ini new URL(href, site).origin beradi (URL va URLSearchParams).
const site = "https://example.com";
const plain = { button: 0, ctrlKey: false, metaKey: false,
shiftKey: false, altKey: false, target: "", href: "/menyu" };Yechim
const site = "https://example.com";
function shouldHandleClick(click) {
const modified =
click.ctrlKey || click.metaKey || click.shiftKey || click.altKey;
if (click.button !== 0 || modified) return false; // brauzerga
if (click.target === "_blank") return false; // yangi tab so'ralgan
return new URL(click.href, site).origin === new URL(site).origin;
}
const plain = { button: 0, ctrlKey: false, metaKey: false,
shiftKey: false, altKey: false, target: "", href: "/menyu" };
const withCtrl = { ...plain, ctrlKey: true };
const middle = { ...plain, button: 1 };
const newTab = { ...plain, target: "_blank" };
const external = { ...plain, href: "https://example.org/" };
console.log(shouldHandleClick(plain)); // true
console.log(shouldHandleClick(withCtrl)); // false
console.log(shouldHandleClick(middle)); // false
console.log(shouldHandleClick(newTab)); // false
console.log(shouldHandleClick(external)); // falsebutton: 1 — sichqonchaning o'rta tugmasi (yangi tab). Tashqi saytga havolada pushState umuman ishlamasdi — boshqa origin'ga yozib bo'lmaydi. Shuning uchun uni ham brauzerga qoldiramiz: oddiy o'tish. React Router (8.4.0) ichidagi shouldProcessLinkClick funksiyasi ham shu shartlarni tekshiradi (tugma, modifikatorlar, target). Tashqi havolani esa Link alohida aniqlaydi — absolyut manzil bo'lsa, oddiy <a> kabi qoldiradi.
4-mashq: vazifalar ga nazar
feat/sahifalar branch'ida «Vazifalar qadami» dagi to'rt o'zgarishni kiriting: base, pages.yml, %BASE_URL%, spa-zaxira plagini va sw.js. Keyin Git Bash'da build qiling:
BASE_PATH=/vazifalar/ npm run builddist/ da 404.html borligini va u index.html bilan bir xil ekanini tekshiring. dist/index.html ni oching: tema.js qaysi yo'l bilan yozilgan?
Yechim
dist/404.html bor, mazmuni dist/index.html bilan bir xil (diff dist/index.html dist/404.html — bo'sh chiqish). Yo'llar absolyut: <script src="/vazifalar/assets/js/tema.js">, manifest — /vazifalar/manifest.webmanifest. BASE_PATH siz build qilsangiz — /assets/js/tema.js: Vercel va lokal sinov uchun. npm test — node testlari 174 ta (yangisi — SW chuqur havola).
11. Real ishda
- Deploy savoli №1: "Sahifani yangilasam 404" — SPA'ni birinchi marta joylashtirgan dasturchining eng ko'p savoli. Javob har hostingda boshqacha, lekin g'oya bitta: zaxira.
- Monorepo va papkadagi ilova: kompaniya saytida ilova ko'pincha
/admin/yoki/app/papkasida. Vitebaseva router'ningbasenamei aynan shu uchun — keyingi darsda. - SEO va birinchi ochilish: SPA'da birinchi HTML deyarli bo'sh. Google JS'ni bajaradi, lekin kechroq va hammasini emas. Ochiq kontentli saytlar (do'kon, blog) shuning uchun sahifani serverda tayyorlaydi — Next.js qismida.
- Intervyu savollari: "SPA va MPA farqi?", "SPA'da F5 bosilsa nima bo'ladi?", "Hash va History routing farqi?", "Router
<a>ni nega ishlatadi?".
Xulosa
- Client routing: havola →
preventDefault+pushState+ qayta chizish; «Orqaga» →popstate. Server bexabar. - Router uch ish qiladi: manzildan sahifa tanlaydi, havolani ushlaydi, tarixni kuzatadi. Manzil — state, sahifa — uning funksiyasi.
- F5 va tashqi havola — server so'rovi. Server har noma'lum yo'lga
index.htmlbersin (SPA zaxirasi); Pages'da —404.htmlnusxasi, holat 404. - Nisbiy yo'llar chuqur havolada buziladi (v8: 4 fayl 404) —
baseabsolyut,%BASE_URL%. - Hash routing zaxirasiz ishlaydi, lekin
#ni egallaydi va mavjud havolalarni o'zgartiradi — kanon History API'ni tanladi.
Keyingi dars: React Router I: sozlash, Route va Link — tayyor router: o'rnatish, marshrutlar, Link, NavLink va vazifalar ning birinchi uch sahifasi.
Manbalar
- HTML Living Standard: "Session history", "The History interface" — html.spec.whatwg.org
- MDN: "Working with the History API", "Window: popstate event" — developer.mozilla.org
- Vite: "Deploying a Static Site" (GitHub Pages,
base), "Env Variables and Modes" (BASE_URL) — vite.dev - Vercel: "Project Configuration — rewrites" — vercel.com/docs
- GitHub Pages: "Creating a custom 404 page" — docs.github.com
react-router8.4.0 manba kodi (createMemoryHistory,getUrlBasedHistory,shouldProcessLinkClick) — npm paketi
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!