Mundarija (31)
- Bu darsda
- 1. Nega bu kerak?
- 2. Qidiruv tizimi SPA'ni qanday ko'radi
- 2.1 Bo'sh vitrina: JS'siz HTML
- 2.2 Googlebot: ikki to'lqin
- 3. React 19: title, meta, link — komponent ichidan
- 3.1 Avtomatik ko'tarish
- 3.2 react-helmet-async — eski kodda
- 4. Canonical: bitta sahifa — bitta manzil
- 4.1 Dublikatlar qayerdan keladi
- 4.2 Kanon qarori: so'rovsiz manzil
- 5. hreflang: bir sahifa — to'rt til
- 5.1 Nega kerak
- 5.2 Kanon: SeoTeglari.tsx
- 6. Soft 404 va noindex
- 7. sitemap.xml va robots.txt
- 8. Prerender, SSR va SSG: qachon SPA yetmaydi
- 8.1 Uch yo'l
- 8.2 Kanon: prerender o'lchanmadi
- 8.3 Qaror daraxti
- 9. Vazifalar qadami: SEO teglari (holat 32 = v11)
- 10. 21-qism yakuni: v10 → v11
- 11. Ko'p uchraydigan xatolar
- 12. Mashqlar
- 1-mashq (oson): bo'shliqlarni to'ldiring
- 2-mashq (o'rta): canonical manzil
- 3-mashq (qiyin): hreflang komponenti
- 4-mashq: Vazifalar qadami
- 13. Real ishda
- Xulosa
- Manbalar
SPA uchun SEO: React ilovani Google qanday ko'radi, canonical, hreflang, sitemap va prerender
Qisqacha: SPA sahifasi serverdan deyarli bo'sh HTML bo'lib keladi — mazmunni JS chizadi. Google JS'ni bajaradi (keyinroq, navbat bilan), lekin Telegram'ning havola ko'rinishi va ko'p botlar bajarmaydi. Shuning uchun: har sahifaga
<title>vadescription(React 19 ularni<head>ga o'zi ko'taradi), dublikatlarga canonical, tillarga hreflang, mavjud bo'lmagan manzilganoindex, havola ko'rinishi uchun statik Open Graph vasitemap.xml. Ommaviy sahifalar ko'p bo'lsa — prerender yoki Next.js/Astro.vazifalarv11: canonical + 5 ta hreflang, 404 —noindex, statik og:*; prerender o'lchanmadi (ilova kirish ortida).
Bu darsda
- CSR sahifani Googlebot va JS bajarmaydigan botlar qanday ko'rishini tushuntirasiz.
- React 19 bilan har sahifaga
title,metavalinkqo'yasiz va natijani<head>da tekshirasiz. - Canonical va hreflang'ni to'g'ri hisoblaydigan komponent yozasiz; soft 404 ni
noindexbilan yopasiz. sitemap.xmlni marshrutlardan yaratasiz va Open Graph nega statik bo'lishi kerakligini bilasiz.- Prerender, SSR va SSG orasida qaror qilasiz: SPA qachon yetadi, qachon Next.js yoki Astro.
Oldin bilishingiz kerak: Qidiruv tizimlari uchun meta teglar, Open Graph, SEO uchun HTML amaliyoti, Hujjat metadata va resurs API'lari, i18n II: til URL'da, Yuklanishni optimallashtirish.
1. Nega bu kerak?
Jasur aka «Bahor» uchun ikki sayt qildirmoqchi. Birinchisi — menyu va manzil: odamlar Google'da "Chilonzor osh" deb qidirib topishi kerak. Ikkinchisi — xodimlar uchun ichki ilova (vazifalar kabi): unga kirish paroli bilan, Google'da chiqishi umuman kerak emas.
Ikkalasi ham React bilan yozilishi mumkin. Lekin SEO talablari butunlay boshqa. Bu dars ikki savolga javob beradi: React SPA'ni qidiruv tizimiga qanday ko'rsatish, va qachon SPA yetmaydi — server tomonda chizish (SSR) yoki oldindan chizish (SSG) kerak bo'ladi.
SEO uchun HTML qismida statik sahifalar uchun qoidalarni ko'rgan edik: sarlavha, tavsif, semantik HTML. SPA'da bitta muammo qo'shiladi: HTML'ning o'zida mazmun yo'q.
Hayotiy o'xshatish: do'kon vitrinasi. Oddiy sayt — tovarlari vitrinada turgan do'kon: o'tkinchi darhol ko'radi. SPA — vitrinasi bo'sh, ichkariga kirganingizda sotuvchi tovarlarni chiqarib qo'yadigan do'kon. Sabrli xaridor (Google) kiradi va ko'radi. Shoshgan o'tkinchi (Telegram havola ko'rinishi) faqat bo'sh vitrinani ko'rib o'tib ketadi.
2. Qidiruv tizimi SPA'ni qanday ko'radi
2.1 Bo'sh vitrina: JS'siz HTML
vazifalar ning index.html i — serverdan keladigan yagona HTML. JS bajarmaydigan bot nimani ko'radi? Uni bot kabi o'qiymiz — faqat matn, regex bilan (HTML qisqartirilgan, CSP va izohlar olib tashlangan):
const html = `<!DOCTYPE html>
<html lang="uz">
<head>
<title>Vazifalar — kichik vazifalar ro'yxati</title>
<meta name="description"
content="React'da yozilgan vazifalar ro'yxati.">
<meta property="og:type" content="website">
<meta property="og:title" content="Vazifalar">
<meta property="og:locale" content="uz_UZ">
<script type="module" src="/assets/index-CKFkeGF8.js"></script>
</head>
<body>
<main>
<h1>Vazifalar</h1>
<div id="ilova"></div>
</main>
</body>
</html>`;
const ol = (re) => html.match(re)?.[1] ?? "(yo'q)";
console.log("title:", ol(/<title>(.*?)<\/title>/));
console.log("og:title:", ol(/property="og:title" content="(.*?)"/));
console.log("canonical:", ol(/rel="canonical" href="(.*?)"/));
const matn = html
.replace(/<head>[\s\S]*<\/head>/, "")
.replace(/<[^>]+>/g, " ")
.split(/\s+/)
.filter(Boolean);
console.log("sahifa matni:", matn.join(" "));Konsolda:
title: Vazifalar — kichik vazifalar ro'yxati
og:title: Vazifalar
canonical: (yo'q)
sahifa matni: VazifalarBot uchun sahifada bitta so'z: «Vazifalar». Ro'yxat, statistika, sozlamalar — hammasi <div id="ilova"> ichida, JS chizgandan keyin. Sarlavha va og:* esa bor — chunki ular HTML'ga qo'lda yozilgan. Canonical yo'q — u JS bilan qo'shiladi (keyinroq ko'ramiz).
2.2 Googlebot: ikki to'lqin
Google'ning hujjatiga ko'ra ("JavaScript SEO basics"), JS sahifa uch bosqichdan o'tadi:
flowchart LR
A["Crawl: HTML yuklandi"] --> B["Havolalar va meta — darhol"]
A --> C["Render navbati"]
C --> D["Headless Chromium: JS bajarildi"]
D --> E["Chizilgan HTML — indeks"]
E --> F["Yangi havolalar — crawl'ga"]Diagrammaga qarang: birinchi to'lqinda Google faqat xom HTML'ni ko'radi. JS bajarilishi uchun sahifa navbatga tushadi — Chromium'ning yangi versiyasi bilan chiziladi. Navbat soniyalar ham, uzoqroq ham bo'lishi mumkin. Natija: Google SPA'ni ko'radi, lekin sekinroq va ishonchliligi kamroq.
Boshqa o'quvchilar esa JS bajarmaydi yoki cheklangan bajaradi:
| Kim | JS bajaradimi | Nimani ko'radi |
|---|---|---|
| Googlebot | ha (navbat bilan) | chizilgan sahifa |
| Telegram, WhatsApp, Facebook havola ko'rinishi | yo'q | faqat HTML'dagi og:* va <title> |
| AI qidiruvlari, boshqa botlar | ko'pincha yo'q | xom HTML |
Shuning uchun asosiy qoida: havola ko'rinishi uchun kerak narsa HTML'da statik bo'lsin. vazifalar og:* teglarini aynan shuning uchun index.html ga qo'ydi (kanon izohi):
<!-- 21/#32: havola ko'rinishi (Telegram, WhatsApp, Facebook) JS'ni
bajarmaydi — faqat shu statik teglar o'qiladi -->
<meta property="og:type" content="website">
<meta property="og:title" content="Vazifalar">
<meta property="og:description"
content="Kichik vazifalar ro'yxati: oflayn ishlaydi, o'zbek, rus va ingliz tilida.">
<meta property="og:locale" content="uz_UZ">Cheklov — TEXNIK-QARZ 69: og:* hamma sahifa uchun bir xil, og:image yo'q (absolyut manzil kerak). SPA'da har sahifaga o'z og:* — faqat prerender yoki server bilan.
Tekshirib ko'ring: Malika Telegram'da do'stiga
vazifalardagi/statistikahavolasini yubordi. Havola ko'rinishida nima chiqadi — "Statistika" sarlavhasimi yoki boshqa narsa?
Javob
«Vazifalar» va «Kichik vazifalar ro'yxati: …» — index.html dagi statik og:* teglar. Telegram JS bajarmaydi, shuning uchun StatistikaSahifasi dagi <title>Statistika — Vazifalar</title> ni ko'rmaydi. Server har manzilga bir xil index.html beradi. Sahifaga xos ko'rinish uchun server har manzilga o'z HTML'ini berishi kerak — prerender yoki SSR.
3. React 19: title, meta, link — komponent ichidan
3.1 Avtomatik ko'tarish
Hujjat metadata darsida ko'rgandik: React 19 da komponent ichida <title>, <meta> va <link> yozsangiz, React ularni <head> ga ko'taradi (hoist). Komponent yo'qolsa — olib tashlaydi. vazifalar da har sahifa 18-qismdan beri o'z sarlavhasini shunday beradi.
Jonli: ikki sahifa, har birida o'z teglari. Statistikaga o'tamiz va <head> ni o'qiymiz:
import { useEffect } from "react";
import {
createMemoryRouter,
Link,
Outlet,
RouterProvider,
useLocation,
} from "react-router";
const SAYT = "https://example.com";
function Seo({ sarlavha, tavsif }) {
const { pathname } = useLocation();
return (
<>
<title>{`${sarlavha} — Bahor`}</title>
<meta name="description" content={tavsif} />
<link rel="canonical" href={SAYT + pathname} />
</>
);
}
function Qobiq() {
const { pathname } = useLocation();
useEffect(() => {
const teglar = document.head.querySelectorAll(
"title, meta[name=description], link[rel=canonical]",
);
console.log(pathname, teglar.length, document.title);
}, [pathname]);
return (
<>
<Link to="/">Menyu</Link>{" "}
<Link to="/statistika">Statistika</Link>
<Outlet />
</>
);
}
const router = createMemoryRouter([
{
path: "/",
element: <Qobiq />,
children: [
{
index: true,
element: <Seo sarlavha="Menyu" tavsif="Osh va manti" />,
},
{
path: "statistika",
element: <Seo sarlavha="Statistika" tavsif="Sotuv" />,
},
],
},
]);
export default function App() {
return <RouterProvider router={router} />;
}Konsolda:
/ 3 Menyu — Bahor
/statistika 3 Statistika — BahorHar sahifada <head> da aynan 3 ta teg — eski sahifaniki olib tashlandi, yangisi qo'yildi. Takror yo'q. (Natija oynasida manzil satri yo'q, shuning uchun xotiradagi router; loyihada — createBrowserRouter.)
Bitta tafsilot: <title> ichida bitta satr bo'lishi kerak. <title>{sarlavha} — Bahor</title> deb yozsangiz, JSX uni ikki bola (satr + satr) qiladi va React dev rejimida ogohlantiradi. Shuning uchun shablon satri: {`${sarlavha} — Bahor`}.
3.2 react-helmet-async — eski kodda
React 19 gacha bu ish uchun react-helmet-async kutubxonasi ishlatilardi: <Helmet><title>…</title></Helmet>, ilova esa <HelmetProvider> ichida. Ko'p mavjud loyihalarda uchratasiz. React 19 da brauzerdagi SPA uchun u ortiqcha — yuqoridagi o'rnatilgan imkoniyat aynan shuni qiladi. Server tomonda chizishda (SSR) <head> ni yig'ish uchun Next.js'ning o'z metadata API'si bor (Metadata API).
4. Canonical: bitta sahifa — bitta manzil
4.1 Dublikatlar qayerdan keladi
Bitta ro'yxat sahifasi bir nechta manzilda ochiladi:
/vazifalar/va/vazifalar/?filtr=faolva/vazifalar/?filtr=bajarilgan(URL — holat);/vazifalarva/vazifalar/(oxirgi chiziqcha);- reklama belgilari bilan:
?utm_source=telegram.
Qidiruv tizimi ularni turli sahifalar deb hisoblashi mumkin — "ball" bo'linadi va qaysi biri natijada chiqishi tasodifga qoladi. Canonical (<link rel="canonical" href="…">) — "bu sahifaning asl manzili shu" degan ko'rsatma (meta teglar). Qidiruv tizimi uni maslahat sifatida qabul qiladi.
4.2 Kanon qarori: so'rovsiz manzil
vazifalar da canonical — location.pathname, ya'ni so'rov qismisiz (?filtr=… — o'sha sahifaning holati, alohida sahifa emas). Har til o'z canonical'iga ega: /ru/statistika ning canonical'i — /ru/statistika, /statistika emas. Tillar dublikat emas — ular hreflang bilan bog'lanadi.
5. hreflang: bir sahifa — to'rt til
5.1 Nega kerak
i18n II darsida vazifalar tilni manzilga qo'ydi: /statistika (o'zbek lotin), /uz-cyrl/statistika, /ru/statistika, /en/statistika. Qidiruv tizimi bular bir sahifaning to'rt tili ekanini qayerdan biladi? hreflang orqali:
<link rel="alternate" hreflang="uz" href="https://…/statistika">
<link rel="alternate" hreflang="ru" href="https://…/ru/statistika">
<link rel="alternate" hreflang="x-default"
href="https://…/statistika">Uch qoida (Google hujjati bo'yicha):
- Har til hamma tillarni ko'rsatadi, o'zini ham. Ruscha sahifada ham to'rttala
alternateturadi. - O'zaro bo'lsin:
uzsahifaruni ko'rsatsa,ruhamuzni ko'rsatishi shart, aks holda e'tiborsiz qoladi. x-default— til mos kelmaganda (masalan, nemis tilidagi foydalanuvchiga) qaysi sahifa.vazifalarda — o'zbekcha.
Til kodi — BCP 47: uz, uz-Cyrl (yozuv bilan), ru, en. Manzildagi prefiks esa kichik harf — uz-cyrl. Bular bir xil narsa emas: biri til kodi, biri manzil bo'lagi.
5.2 Kanon: SeoTeglari.tsx
Kanon komponenti — kod aynan (fayl boshidagi izoh qisqartirildi):
import { useLocation } from "react-router";
import { TILLAR } from "../i18n/index.ts";
import { tildaYol } from "../i18n/yol.ts";
export function SeoTeglari() {
// pathname — basename'siz; to'liq yo'l — brauzer manzilidan
useLocation();
const toliq = (til: (typeof TILLAR)[number]) =>
new URL(
tildaYol(location.pathname, "", til, import.meta.env.BASE_URL),
location.origin,
).href;
return (
<>
<link
rel="canonical"
href={new URL(location.pathname, location.origin).href}
/>
{TILLAR.map((til) => (
<link
key={til}
rel="alternate"
hrefLang={til}
href={toliq(til)}
/>
))}
<link rel="alternate" hrefLang="x-default" href={toliq("uz")} />
</>
);
}Uch nozik joy:
useLocation()— qiymati ishlatilmaydi. U faqat obuna uchun: manzil o'zgarganda komponent qayta chiziladi. To'liq yo'l esawindow.locationdan olinadi, chunki routerpathnamei basename'siz (/ruva ilova papkasi kesilgan). Bu — i18n II dagibasenameqarorining natijasi.tildaYol(…, "", til, …)— ikkinchi parametr bo'sh: so'rov qismi (?filtr=) hreflang'ga ham kirmaydi.hrefLang— React'da camelCase. HTML'gahreflangbo'lib tushadi.
Qobiq.tsx da <SeoTeglari /> bir marta — har sahifa uchun. React 19 uni <head> ga ko'taradi.
6. Soft 404 va noindex
SPA'da mavjud bo'lmagan manzil (/vazifalar/xatoli-sahifa) ham ko'pincha 200 javob oladi: server har manzilga index.html beradi (SPA zaxirasi), "Sahifa topilmadi" ni esa JS chizadi. Qidiruv tizimi uchun bu "soft 404": status — "bor", mazmun — "yo'q". Bunday sahifalar indeksga tushishi mumkin.
vazifalar ning ikki xostingi bu yerda farq qiladi (kanon izohlari): GitHub Pages 404.html ni haqiqiy 404 status bilan beradi, Vercel'ning rewrites i esa 200 bilan. Ikkalasida ham ishlaydigan yechim — 404 sahifada noindex (kanon):
<title>
{t("sahifaNomi", { sahifa: t("topilmadi.sarlavha") })}
</title>
{/* 21/#32: SPA zaxirasi (Vercel rewrites) 404 manzilga ham 200
beradi — "soft 404": qidiruv tizimi indekslamasin */}
<meta name="robots" content="noindex" />Google JS chizgandan keyin noindex ni ko'radi va sahifani indeksga olmaydi. Ehtiyot bo'ling: noindex ni boshqa sahifadan "yuqtirib" olmang. React 19 uni sahifadan chiqqanda olib tashlaydi — shuning uchun komponent ichida yozish xavfsiz.
Tekshirib ko'ring: Nega
noindexniindex.htmlga yozib, keyin JS bilan "kerakli sahifalarda olib tashlash" yomon g'oya?
Javob
Googlebot xom HTML'da noindex ni ko'rsa, sahifani render navbatiga umuman qo'ymasligi mumkin — JS hech qachon bajarilmaydi va "olib tashlash" ishlamaydi. Google hujjati ham shuni ogohlantiradi: noindex ni JS bilan qo'shish mumkin, olib tashlashga tayanmang. Kanon aksincha qiladi: HTML'da noindex yo'q, faqat 404 sahifa uni qo'shadi.
7. sitemap.xml va robots.txt
Sitemap — saytdagi sahifalar ro'yxati (XML). SPA'da Google havolalarni JS bajarilgandan keyin topadi; sitemap esa ularni darhol beradi. Ko'p tilli saytda sitemap hreflang'ni ham berishi mumkin. Marshrutlar ro'yxatidan build vaqtida yaratish — oddiy Node skripti:
const SAYT = "https://example.com/menyu";
const TILLAR = [["uz", ""], ["ru", "/ru"], ["en", "/en"]];
const SAHIFALAR = ["/", "/osh", "/manzil"];
const manzil = (prefiks, yol) =>
SAYT + prefiks + (yol === "/" ? "/" : yol);
function sitemap() {
const qatorlar = [];
for (const yol of SAHIFALAR) {
for (const [, prefiks] of TILLAR) {
qatorlar.push("<url>", ` <loc>${manzil(prefiks, yol)}</loc>`);
for (const [til, p] of TILLAR) {
qatorlar.push(
` <xhtml:link rel="alternate" hreflang="${til}"` +
` href="${manzil(p, yol)}"/>`,
);
}
qatorlar.push("</url>");
}
}
return qatorlar;
}
const q = sitemap();
console.log("url:", q.filter((x) => x === "<url>").length);
console.log(q.slice(0, 6).join("\n"));Konsolda:
url: 9
<url>
<loc>https://example.com/menyu/</loc>
<xhtml:link rel="alternate" hreflang="uz" href="https://example.com/menyu/"/>
<xhtml:link rel="alternate" hreflang="ru" href="https://example.com/menyu/ru/"/>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/menyu/en/"/>
</url>3 sahifa × 3 til = 9 ta <url>, har birida uchala til. To'liq faylga <?xml …?> sarlavhasi va <urlset xmlns="…" xmlns:xhtml="…"> o'rami qo'shiladi (sitemaps.org protokoli). Faylni dist/sitemap.xml ga yozib, robots.txt da ko'rsatasiz: Sitemap: https://…/sitemap.xml.
vazifalar da sitemap ham, robots.txt ham yo'q (TEXNIK-QARZ 69). Sabab: ilovaning ommaviy sahifasi faqat /kirish; qolgani kirish ortida — qidiruvga keraksiz. robots.txt esa domen ildizida bo'lishi kerak (login.github.io/robots.txt), loyiha sayti (login.github.io/vazifalar/) uni o'zi qo'ya olmaydi.
8. Prerender, SSR va SSG: qachon SPA yetmaydi
8.1 Uch yo'l
| Yo'l | HTML qachon yaratiladi | Misol |
|---|---|---|
| CSR (SPA) | brauzerda, JS bilan | vazifalar |
| SSG / prerender | build vaqtida, har sahifa uchun tayyor fayl | blog, menyu sayti, hujjatlar |
| SSR | har so'rovda serverda | do'kon, yangiliklar, shaxsiy sahifalar |
Prerender (build vaqtida oldindan chizish) — SPA'ni o'zgartirmasdan, build oxirida har marshrutni Node'da chizib, dist/osh/index.html kabi fayllarga yozish. Brauzer avval tayyor HTML'ni ko'radi (bot ham!), keyin React uni "jonlantiradi" (hydration). Bu atama 18-qismdagi <Activity> prerender'idan farq qiladi: u yerda React yashirin UI'ni xotirada oldindan chizardi, bu yerda — build'da HTML faylga.
Vite uchun vositalar (hujjat bo'yicha): vite-prerender-plugin — har marshrut uchun prerender() funksiyangizni chaqirib HTML yozadi; vite-ssg — asosan Vue uchun, React uchun uning vite-react-ssg varianti. React'ning o'zida ham build vaqtida chizish API'si bor: react-dom/static dagi prerender (React 19). Next.js va Astro esa SSG'ni "qutidan" beradi. Bu vositalarning hech biri kanonda sinab ko'rilmagan — ular haqidagi gaplar faqat hujjatga tayanadi.
8.2 Kanon: prerender o'lchanmadi
Halol holat: vazifalar da prerender sinab ko'rilmadi va o'lchanmadi — bu darsda "prerender LCP'ni shuncha yaxshiladi" degan raqam yo'q. Sabab: ilova kirish ortida. Prerender qilinadigan ommaviy sahifa — faqat /kirish; ro'yxat, statistika va sozlamalar foydalanuvchining shaxsiy ma'lumoti, ularni oldindan chizib bo'lmaydi. Qaror — TEXNIK-QARZ 69: SSG/SSR kerak bo'lgan joy — 23-qismdagi Next.js portfolio.
8.3 Qaror daraxti
flowchart TB
A{"Sahifa qidiruvda chiqishi kerakmi?"} -->|yo'q, kirish ortida| B["SPA yetadi: title, noindex 404"]
A -->|ha| C{"Mazmun build'da ma'lummi?"}
C -->|ha: menyu, blog| D["SSG / prerender: Astro, Next.js"]
C -->|yo'q: narx, qoldiq| E["SSR: Next.js"]
D --> F["har sahifaga o'z og:*, sitemap"]
E --> FDiagrammaga qarang: birinchi savol texnologiya haqida emas, mahsulot haqida. «Bahor»ning ichki ilovasi — SPA. Menyu sayti — SSG (Astro ham, Next.js ham yaxshi). Onlayn buyurtma va narxlar har daqiqa o'zgaradigan do'kon — SSR.
Astro — mazmunli saytlar uchun framework: sahifa sukut bo'yicha JS'siz HTML, faqat interaktiv qismlar ("orollar") React bilan (orol g'oyasi). Next.js — React'ning to'liq framework'i: SSG, SSR va server komponentlar bitta loyihada. Uni 23-qismda o'rganamiz.
9. Vazifalar qadami: SEO teglari (holat 32 = v11)
git switch -c feat/seoO'zgarishlar (kanon):
src/komponentlar/SeoTeglari.tsx— «Kanon:SeoTeglari.tsx» bo'limidagi komponent;Qobiq.tsxda<SeoTeglari />.TopilmadiSahifasi.tsx—<meta name="robots" content="noindex" />.index.html— statik og:* (to'rt teg).- Hujjatlar: README (v11 bo'limi), AGENTS.md («21-qism qoidalari»), TEXNIK-QARZ v11 (60–70 qatorlar).
Tekshiruvlar (haqiqiy, holat 32):
- Build: birinchi yuklash JS 206.7 KB (holat 31 — 206.6; SEO komponenti +0.1), CSS 5.1.
npm test— node:test 255, Vitest 65; lint,format:check,tip,knip— toza.- e2e-21 — 81. SEO bo'limi (
natija-21-32.txt):
✅ JS'siz: lang, title, description, og:title/description/type: [true,true,"React'da yozilgan vazifalar ro'yxati.","Vazifalar",true,"website"]
✅ JS'siz: canonical/hreflang YO'Q (faqat JS bilan): false
✅ JS bilan: canonical (so'rovsiz), hreflang 4 til + x-default, <head> da, title: {"canonical":"http://127.0.0.1:5718/ru/statistika","alt":[["uz","/statistika"],["uz-Cyrl","/uz-cyrl/statistika"],["ru","/ru/statistika"],["en","/en/statistika"],["x-default","/statistika"]],"title":"Статистика — Задачи","ichida":true}
✅ sahifa almashdi — canonical ham (bitta): ["/ru/sozlamalar"]
✅ 404 sahifa: robots noindex (soft 404): "noindex"
✅ konsolda xato yo'q: []Ikkinchi qatorga e'tibor bering — test halol cheklovni ham tekshiradi: JS'siz HTML'da canonical va hreflang yo'q. Uchinchi qator — ruscha statistika sahifasi: canonical o'zi, beshta alternate, ruscha sarlavha. To'rtinchisi — sahifa almashganda canonical bitta qoladi (eskisi olib tashlandi).
git add -A
git commitCommit (kanon): feat: canonical, hreflang, 404 noindex, og:*; v11 hujjatlari.
10. 21-qism yakuni: v10 → v11
Bu dars bilan vazifalar v11 tayyor. 21-qism davomida nima o'zgardi (kanon yakuniy holati):
| Ko'rsatkich | v10 | v11 |
|---|---|---|
| Birinchi yuklash JS / CSS (gzip) | 201.8 / 4.6 KB | 206.7 / 5.1 KB |
| node:test / Vitest | 203 / 65 | 255 / 65 |
| Brauzer testlari | 159, 72, 100, 77 | 159, 72, 100, 77 + e2e-21 81 + dev-21 15 |
| Lighthouse mobil (kirgan / yangi, bir sessiyada) | 89 / 93 | 88 / 92 (holat 31) |
Lighthouse qatoriga e'tibor: 20-qism oxirida v10 uchun 93 / 94 yozilgan edi. Bu yerdagi 89 / 93 — o'sha kodning boshqa sessiyadagi o'lchovi; v10 va v11 bitta sessiyada o'lchangani uchun faqat ular o'zaro solishtiriladi (Yuklanishni optimallashtirish darsidagi izoh).
Accessibility, Best Practices, SEO — 100; axe — 0 buzilish (21-qismda 6 ta yangi ko'rinish qo'shildi); CLS — 0.
Kirdi:
- to'rt til — react-i18next, til manzilda, kirill skript bilan;
- Markdown izoh (kech, XSS 0), o'z SVG teglar grafigi, rasm kesish;
- CSV eksport/import, QR (kech), «Joyini tanlab saqlash»;
- o'z analitika va xatolar kuzatuvi (production'da manzilsiz), feature flag'lar;
- AI yordamchi (dev'da soxta oqim), SW «Yangilash», oflayn navbat;
- kech sahifalar, canonical va hreflang.
O'lchandi, lekin kirmadi:
- Recharts (+103.4 KB va tsc xatolari), posthog (Trusted Types), Sentry (DSN = hisob);
- vite-plugin-pwa va Serwist (ro'yxatdan o'tkazgich Trusted Types'da);
- react-easy-crop (
innerHTML), Vercel AI SDK (+44.1 KB, Trusted Types); - jami 58 ta kutubxona misoli CSP ostida o'lchandi.
Halol qarzlar: web push lokal yetkazilmadi; prerender o'lchanmadi; oflayn navbat faqat navigator.onLine bo'yicha; i18n +17 KB birinchi yuklashda.
Testlar topgan haqiqiy xatolar — 21-qismning eng muhim saboqlaridan biri. e2e va o'lchov skriptlari quyidagilarni topdi, ular tuzatildi:
- skip link boshqa sahifalarda yo'q joyga ko'rsatardi (v10 dan beri);
- izoh va rasm kesish oynasi yopilganda fokus yo'qolardi;
- GFM katakchalarida yorliq yo'q edi (axe — critical);
- deploydan keyin ochiq sahifa kech chunk'ni ocholmasdi (PWA);
- Chrome'da o'zbek lotin
Intlsanasi yo'q; - analitika
visibilitychangeniwindowda tinglardi; - soxta AI serverida
req"close" darhol kelardi (AI chat).
Bularning hech birini qo'lda sinash topmagan bo'lardi. Shu qism davomida biz testlarni "sahna ortida" yozdik: puppeteer skriptlari, node:test, axe. Keyingi qismda ularni o'zingiz yozishni o'rganasiz: Vitest, React Testing Library, MSW bilan API'ni soxtalashtirish, haqiqiy brauzerda komponent testi va Playwright bilan e2e. vazifalar ning testlari endi sizning qo'lingizda bo'ladi.
11. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Telegram havolasida eski/umumiy sarlavha | og:* JS bilan qo'yilgan | og:* — HTML'da statik (yoki prerender) |
<head> da ikki canonical |
teg qo'lda appendChild qilingan |
React 19 <link> — komponentda |
| hreflang e'tiborsiz | o'zaro emas yoki o'zini ko'rsatmaydi | har til — hamma tillar + x-default |
?filtr=… sahifalari indeksda |
canonical so'rov bilan | pathname — so'rovsiz |
| 404 sahifalar qidiruvda | soft 404 (200) | noindex |
<title> ogohlantirish beradi |
ikki bola ({x} — Bahor) |
bitta satr: {`${x} — Bahor`} |
12. Mashqlar
Mashqlar kurs/mashqlar/21/32-spa-seo/ da.
1-mashq (oson): bo'shliqlarni to'ldiring
- Sahifaning asl manzilini ko'rsatuvchi
link— rel="" - Boshqa tildagi nusxani ko'rsatuvchi atribut —
- Til mos kelmaganda ko'rsatiladigan qiymat —
- Soft 404 sahifaga qo'yiladigan
robotsqiymati — - Telegram havola ko'rinishi uchun sarlavha — og:
Yechim
- canonical — dublikatlar orasida asli.
- hreflang — React'da
hrefLang. - x-default —
vazifalarda o'zbekcha sahifa. - noindex — indeksga olinmasin.
- title —
og:title, HTML'da statik.
2-mashq (o'rta): canonical manzil
Funksiya canonical(manzil) yozing: to'liq manzildan canonical yasaydi — so'rov qismi (?…) va #… olib tashlanadi, yo'l kichik harfga o'tadi, oxirgi / olib tashlanadi (faqat ildiz / qoladi). Tekshiring:
console.log(canonical("https://example.com/Menyu/Osh/?utm=tg#narx"));
// https://example.com/menyu/oshIshora: URL obyekti — origin va pathname alohida.
Yechim
function canonical(manzil) {
const url = new URL(manzil);
let yol = url.pathname.toLowerCase();
if (yol.length > 1 && yol.endsWith("/")) yol = yol.slice(0, -1);
return url.origin + yol;
}
console.log(canonical("https://example.com/Menyu/Osh/?utm=tg#narx"));
console.log(canonical("https://example.com/?filtr=faol"));
console.log(canonical("https://example.com/ru/statistika"));Konsolda:
https://example.com/menyu/osh
https://example.com/
https://example.com/ru/statistikaurl.origin so'rov va # ni o'zi qoldirib ketadi. Kichik harf — saytingiz qoidasi bo'lsa (server ham shunday yo'naltirsa). vazifalar kanoni kichik harfga o'tkazmaydi va chiziqchaga tegmaydi — uning manzillarini router yaratadi, ular allaqachon bir xil.
3-mashq (qiyin): hreflang komponenti
«React 19» bo'limidagi jonli misolga hreflang qo'shing. Tillar: uz (prefikssiz), ru (/ru), en (/en). Talablar:
- Har sahifada uch
alternate+x-default(o'zbekcha). - Joriy manzil til prefiksi bilan bo'lishi mumkin:
/ru/statistikada ham uchala variant to'g'ri (/statistika,/ru/statistika,/en/statistika). - Konsolga
<head>dagialternatelar soni varuhavolasi.
Ishora: avval prefiksni olib tashlang (yol.replace(/^\/(ru|en)(?=\/|$)/, "")), keyin har til prefiksini qo'shing. Bo'sh qolsa — /.
Yechim
import { useEffect } from "react";
import {
createMemoryRouter,
Link,
Outlet,
RouterProvider,
useLocation,
} from "react-router";
const SAYT = "https://example.com";
const TILLAR = [["uz", ""], ["ru", "/ru"], ["en", "/en"]];
function asosiyYol(yol) {
return yol.replace(/^\/(ru|en)(?=\/|$)/, "") || "/";
}
function tildaManzil(prefiks, yol) {
const toliq = prefiks + (yol === "/" ? "" : yol);
return SAYT + (toliq || "/");
}
function Hreflang() {
const yol = asosiyYol(useLocation().pathname);
return (
<>
{TILLAR.map(([til, prefiks]) => (
<link
key={til}
rel="alternate"
hrefLang={til}
href={tildaManzil(prefiks, yol)}
/>
))}
<link
rel="alternate"
hrefLang="x-default"
href={tildaManzil("", yol)}
/>
</>
);
}
function Qobiq() {
const { pathname } = useLocation();
useEffect(() => {
const alt = document.head.querySelectorAll("link[rel=alternate]");
const ru = document.head.querySelector("link[hreflang=ru]");
console.log(pathname, alt.length, ru.getAttribute("href"));
}, [pathname]);
return (
<>
<Hreflang />
<Link to="/ru/statistika">Ruscha statistika</Link>
<Outlet />
</>
);
}
const router = createMemoryRouter([
{
path: "/",
element: <Qobiq />,
children: [
{ index: true, element: <h2>Menyu</h2> },
{ path: "ru/statistika", element: <h2>Статистика</h2> },
],
},
]);
export default function App() {
return <RouterProvider router={router} />;
}Konsolda:
/ 4 https://example.com/ru
/ru/statistika 4 https://example.com/ru/statistikaIkkala sahifada ham to'rtta alternate — takror yo'q. Bosh sahifada ru havolasi /ru (oxirida chiziqchasiz — tildaManzil bo'sh yo'lni qo'shmaydi). Ruscha statistikada prefiks olib tashlanib, qayta qo'yildi — /ru/ru/statistika bo'lib qolmadi. Kanon xuddi shu ishni yoldanTil va tildaYol bilan qiladi (i18n II).
4-mashq: Vazifalar qadami
feat/seo branch'ida «Vazifalar qadami» dagi o'zgarishlarni qiling va v11 ni yakunlang: README, AGENTS.md, TEXNIK-QARZ.
Yechim
Tekshiruv ro'yxati:
npm run build && npm run preview. DevTools → Elements →<head>:/ru/statistikada bitta canonical va beshtaalternate.- Sahifa manbasi (Ctrl+U) — JS'siz HTML: og:* bor, canonical yo'q (kutilgan).
/mavjud-emas—<meta name="robots" content="noindex">.- Lighthouse → SEO — 100.
npm test— node 255, Vitest 65; birinchi yuklash ~206.7 KB.git tag v11(kanon tartibi bo'yicha, ixtiyoriy).
13. Real ishda
- Marketing saytlari (landing, blog, menyu) — SEO asosiy talab; ko'pincha Next.js yoki Astro bilan. Ichki ilovalar (admin, CRM,
vazifalar) — SPA, SEO deyarli kerak emas,noindexbilan. - Kod ko'rigida savollar: "bu sahifa havola sifatida ulashilsa nima ko'rinadi?", "canonical bitta-mi?", "hreflang o'zaro-mi?".
- Intervyu savollari: "Google SPA'ni indekslaydimi?", "CSR, SSR, SSG farqi?", "canonical nima uchun?", "soft 404 nima?", "qachon Next.js tanlaysiz?".
Xulosa
- SPA HTML'i deyarli bo'sh. Google JS'ni navbat bilan bajaradi; havola ko'rinishi va ko'p botlar — bajarmaydi. Havola uchun kerak narsa — HTML'da statik.
- React 19:
<title>,<meta>,<link>— komponentda,<head>ga o'zi ko'tariladi va almashadi (react-helmet-async — eski kodda). - Canonical — so'rovsiz manzil; hreflang — har til hamma tillarni, o'zaro,
x-defaultbilan. - Soft 404 —
noindex; sitemap — marshrutlardan build'da;robots.txt— domen ildizida. - Qidiruv kerak bo'lsa: build'da ma'lum mazmun — SSG/prerender, o'zgaruvchan — SSR.
vazifalarda prerender o'lchanmadi — ilova kirish ortida. - 21-qism: v10 → v11 — to'rt til, oflayn navbat, «Yangilash», kech sahifalar; birinchi yuklash 206.7 KB; testlar 7 ta haqiqiy xatoni topdi.
Keyingi dars: Nega test yozamiz va frontend test turlari — 22-qism boshlanadi: shu qismda sahna ortida ishlagan testlarni endi o'zingiz yozasiz.
Manbalar
- Google Search Central: "Understand the JavaScript SEO basics", "Consolidate duplicate URLs" (canonical), "Localized versions of your page" (hreflang), soft 404,
noindexva JavaScript - React 19:
<title>,<meta>,<link>komponentlari (react.dev, 2024-12) - sitemaps.org: Sitemap protokoli; Google: sitemap'da hreflang (
xhtml:link) - vite-prerender-plugin, vite-react-ssg,
react-dom/staticprerender— hujjatlar - Kanon:
vazifalarholat 32 (= v11),SeoTeglari.tsx,natija-21-32.txt,tekshiruv-32.txt, TEXNIK-QARZ v11 (69) (2026-10-17)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!