IlmHamroh
JavaScript Full-stack/23-qism. Next.js30/43-dars15 daqiqa
Mundarija (33)

Next.js SEO fayllari: sitemap.ts, robots.ts, manifest va OG rasmlar

Qisqacha: Next.js app/ papkasidagi maxsus nomli fayllardan SEO fayllarini o'zi yasaydi: sitemap.ts → /sitemap.xml, robots.ts → /robots.txt, manifest.ts → /manifest.webmanifest, icon.svg → favicon. opengraph-image.tsx esa JSX'dan PNG rasm chizadi (ImageResponse) — ijtimoiy tarmoqdagi havola rasmi. Bu fayllar ham statik (○) bo'lib, build vaqtida tayyorlanadi. JSON-LD — sahifa ichidagi tuzilgan ma'lumot; uni < belgisini qochirib yozish shart.

Bu darsda

  • sitemap.ts ni bitta manbadan (loyihalar va postlar ro'yxatidan) yasaysiz va ko'p sitemap qachon kerakligini bilasiz.
  • robots.ts va manifest.ts ni yozasiz va haqiqiy javoblarini ko'rasiz.
  • opengraph-image.tsx bilan sayt va har post uchun OG rasm chizasiz; satori'ning cheklovlarini bilasiz.
  • Bosh sahifaga JSON-LD qo'shasiz va nega < ni qochirish kerakligini jonli ko'rasiz.
  • portfolio holat 30 ni commit qilasiz.

Oldin bilishingiz kerak: Metadata API, SPA uchun SEO, generateStaticParams, Route Handlers, XSS.

1. Nega bu kerak?

Oldingi darsda har sahifa o'z "muqovasini" oldi: sarlavha, tavsif, canonical. Lekin qidiruv tizimi saytga kirganda yana uchta savol beradi:

  1. Saytda qanday sahifalar bor? — sitemap.
  2. Qaysi joylarga kirmaslik kerak? — robots.txt.
  3. Telegram'da havola qanday rasm bilan ko'rinadi? — Open Graph rasmi.

SPA uchun SEO darsida bularni qo'lda yozgansiz: public/sitemap.xml faylini qo'lda to'ldirish, OG rasmni Figma'da chizish. Muammosi ma'lum: yangi blog posti qo'shdingiz — sitemap'ni yangilashni unutdingiz. Post sarlavhasini o'zgartirdingiz — OG rasmdagi eski sarlavha qoldi.

Next'da bu fayllar kod bo'ladi. Ma'lumot bitta joyda (loyihalar massivi, blog/ papkasi) — sitemap ham, OG rasm ham undan yasaladi.

Bitta manbadan bir nechta fayl:

flowchart LR
  D["LOYIHALAR + blog/*.md"] --> S["sitemap.ts"]
  D --> O["opengraph-image.tsx"]
  D --> M["generateMetadata"]
  S --> SX["/sitemap.xml"]
  O --> P["PNG 1200×630"]
  M --> H["og: teglar"]
  R["robots.ts"] --> RX["/robots.txt"]
  RX -.->|Sitemap:| SX

Chapda — ma'lumot, o'ngda — build natijasi. Post qo'shilsa, uchala o'ng tomon o'zi yangilanadi. robots.txt esa botga sitemap qayerdaligini aytadi (nuqtali chiziq).

Hayotiy o'xshatish: kutubxona. Sitemap — katalog kartochkalari, robots.txt — "Xodimlar uchun" eshigidagi yozuv, OG rasm — kitob muqovasining fotosurati. Yaxshi kutubxonachi kartochkalarni qo'lda emas, kitoblar ro'yxatidan avtomatik chiqaradi.

2. sitemap.ts

2.1 Kod

Kanondagi app/sitemap.ts (holat 30):

ts
import type { MetadataRoute } from "next";
import { postlar } from "@/malumot/blog";
import { LOYIHALAR } from "@/malumot/loyihalar";
import { SAYT_URL } from "@/malumot/sozlama";

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const oddiy = ["", "/haqimda", "/loyihalar", "/blog", "/aloqa"].map((y) => ({
    url: `${SAYT_URL}${y}`,
  }));
  const loyihalar = LOYIHALAR.map((l) => ({
    url: `${SAYT_URL}/loyihalar/${l.slug}`,
    lastModified: `${l.sana}-01`,
  }));
  const blog = (await postlar()).map((p) => ({
    url: `${SAYT_URL}/blog/${p.slug}`,
    lastModified: p.sana,
  }));
  return [...oddiy, ...loyihalar, ...blog];
}
Qism Nima
MetadataRoute.Sitemap qaytadigan massiv turi: url, lastModified, changeFrequency, priority
oddiy 5 ta doimiy sahifa
loyihalar har loyiha — sana ("2026-10") dan lastModified
blog postlar fayllardan — await postlar()

Yangi post qo'shsangiz — sitemap'ni qo'lda tahrirlamaysiz, keyingi build uni o'zi qo'shadi.

2.2 Haqiqiy natija

Build jadvalida (natijalar/tekshiruv-30.txt):

text
└ ○ /sitemap.xml                                              30d      1y

○ — statik. 30d 1y — postlar keshining umri (Cache Components II): sitemap postlarga bog'liq, shuning uchun ular bilan birga yangilanadi. next start dagi javob (natijalar/30-seo.txt, boshi):

text
/sitemap.xml 200 application/xml
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com</loc>
</url>
<url>
<loc>https://example.com/loyihalar/vazifalar</loc>
<lastmod>2026-10-01</lastmod>
</url>

Jami 11 ta URL: 5 oddiy + 3 loyiha + 3 post. Bosh sahifa manzili oxirida / siz — canonical bilan bir xil (oldingi darsdagi o'lchov).

2.3 Natija oynasida: sitemap mantiqi

sitemap() ning ishi — oddiy massiv yasash. Uni Next'siz yozib, XML'ga aylantiramiz:

js
const SAYT = "https://example.com";
const LOYIHALAR = [
  { slug: "vazifalar", sana: "2026-10" },
  { slug: "bahor", sana: "2026-09" },
];

const royxat = [
  ...["", "/aloqa"].map((y) => ({ url: SAYT + y })),
  ...LOYIHALAR.map((l) => ({
    url: `${SAYT}/loyihalar/${l.slug}`,
    lastModified: `${l.sana}-01`,
  })),
];

for (const s of royxat) {
  const lastmod = s.lastModified ? ` ${s.lastModified}` : "";
  console.log(`${s.url}${lastmod}`);
}
console.log(`jami: ${royxat.length}`);

Konsolda:

text
https://example.com
https://example.com/aloqa
https://example.com/loyihalar/vazifalar 2026-10-01
https://example.com/loyihalar/bahor 2026-09-01
jami: 4

Next shu massivni yuqoridagi XML'ga aylantiradi. lastModified — Google'ga "bu sahifa qachon o'zgargan" ishorasi: o'zgarmagan sahifani qayta tekshirishga vaqt sarflamaydi.

2.4 Ko'p sitemap

Google bitta sitemap'da 50 000 URL'gacha qabul qiladi. Katta do'konda 200 000 mahsulot bo'lsa — sitemap bo'linadi. Next'da ikki yo'l:

  • har bo'limda o'z fayli: app/sitemap.ts va app/mahsulotlar/sitemap.ts;
  • generateSitemaps() — [{ id: 0 }, { id: 1 }, …] qaytaradi, sitemap({ id }) har bo'lakni yasaydi (/mahsulotlar/sitemap/0.xml …).

portfolio da 11 ta URL — bitta fayl yetarli.

Tekshirib ko'ring: Kanondagi sitemap'da /loyihalar/vazifalar/rasm (rasm sahifasi) yo'q. Bu xatomi?

Javob

Yo'q, ataylab. Rasm sahifasi — loyiha skrinshotining kattalashtirilgan ko'rinishi, alohida qimmatli matni yo'q. Sitemap'ga faqat Google'da topilishi kerak bo'lgan sahifalar kiradi. Rasm sahifasi baribir o'z canonical'iga ega va havola orqali topiladi.

3. robots.ts va manifest.ts

3.1 robots

ts
import type { MetadataRoute } from "next";
import { SAYT_URL } from "@/malumot/sozlama";

export default function robots(): MetadataRoute.Robots {
  return {
    rules: { userAgent: "*", allow: "/", disallow: "/api/" },
    sitemap: `${SAYT_URL}/sitemap.xml`,
  };
}

Haqiqiy javob (natijalar/30-seo.txt):

text
/robots.txt 200 text/plain
User-Agent: *
Allow: /
Disallow: /api/

Sitemap: https://example.com/sitemap.xml

Disallow: /api/ — qidiruv botlari /api/qayta-yangila ga kirmasin. Bu himoya emas: robots.txt — iltimos, buyruq emas. Yomon niyatli bot uni o'qib, aynan yopiq yo'llarni qidiradi. Haqiqiy himoya — handler ichidagi sir tekshiruvi (Route Handlers).

3.2 manifest

manifest.ts — PWA darsidagi manifest.webmanifest ning kod ko'rinishi:

text
/manifest.webmanifest 200 application/manifest+json
{"name":"Aziz Karimov — portfolio","short_name":"Aziz","lang":"uz","start_url":"/","display":"browser","background_color":"#f7faf8","theme_color":"#1f6f4a","icons":[{"src":"/icon.svg","sizes":"any","type":"image/svg+xml"}]}

display: "browser" — portfolio o'rnatiladigan ilova emas, oddiy sayt bo'lib qoladi. Manifest bu yerda brauzerga nom, rang va ikonkani aytadi. Next uni <head> ga <link rel="manifest"> bilan o'zi ulaydi.

3.3 Ikonkalar

app/icon.svg ni siz Layout darsida qo'ygansiz — u favicon bo'ldi. Boshqa nomlar ham bor:

Fayl Nima
favicon.ico eski favicon (faqat app/ ildizida)
icon.png, icon.svg zamonaviy favicon
apple-icon.png iPhone bosh ekraniga qo'shilganda
icon.tsx kod bilan chiziladigan ikonka (ImageResponse)

4. OG rasmlar — opengraph-image.tsx

4.1 JSX'dan PNG

opengraph-image.tsx — ImageResponse qaytaradigan komponent. Next uni build vaqtida bajarib, PNG fayl qiladi. Kanondagi sayt rasmi (holat 30, qisqartirilgan — logotip bloki olib tashlangan):

tsx
import { ImageResponse } from "next/og";

export const alt = "Aziz Karimov — frontend dasturchi";
export const size = { width: 1200, height: 630 };
export const contentType = "image/png";

export default function OgRasm() {
  return new ImageResponse(
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        justifyContent: "center",
        width: "100%",
        height: "100%",
        padding: 80,
        background: "#f7faf8",
        color: "#1f2a24",
      }}
    >
      <div style={{ fontSize: 72, fontWeight: 700 }}>
        Aziz Karimov
      </div>
      <div style={{ marginTop: 16, fontSize: 40 }}>
        Frontend dasturchi — loyihalar, blog, case study
      </div>
    </div>,
    size,
  );
}
Eksport Ma'nosi
alt og:image:alt — rasm tavsifi (ekran o'quvchi va bot uchun)
size 1200×630 — Facebook va Telegram tavsiya etgan nisbat
contentType og:image:type

ImageResponse ichida satori kutubxonasi ishlaydi: u JSX'ni SVG'ga, keyin PNG'ga aylantiradi. Bu brauzer emas, shuning uchun CSS cheklangan. Next hujjati (ImageResponse) va satori qoidalaridagi cheklovlar:

  • faqat flexbox va CSS'ning bir qismi — display: grid ishlamaydi;
  • har div da bir nechta bola bo'lsa, display: "flex" aniq yozilishi kerak;
  • shrift — faqat ttf, otf, woff (woff2 emas);
  • jami hajm (JSX, shriftlar, rasmlar) — 500 KB gacha.

4.2 Har post uchun rasm

app/(sayt)/blog/[slug]/opengraph-image.tsx — dinamik segment ichida. Unga ham params keladi, va u ham generateStaticParams bilan oldindan tayyorlanadi:

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

export default async function PostOgRasmi({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const p = await postniOl((await params).slug);
  return new ImageResponse(
    <div style={{ display: "flex", flexDirection: "column" }}>
      <div style={{ fontSize: 36 }}>Aziz Karimov · blog</div>
      <div style={{ fontSize: 68, fontWeight: 700 }}>
        {p?.sarlavha ?? "Blog"}
      </div>
    </div>,
    size,
  );
}

Uslublar qisqartirildi (to'liq kod — holat 30 da: fon, padding, teglar qatori). Build jadvali:

text
├   /blog/[slug]/opengraph-image-zrbhtn
│ ├ ● /blog/portfolio-v2/opengraph-image-zrbhtn               30d      1y
│ ├ ● /blog/server-components/opengraph-image-zrbhtn          30d      1y
│ └ ● /blog/vazifalar-testlari/opengraph-image-zrbhtn         30d      1y
├ ○ /opengraph-image                                          30d      1y

Ikki yangi narsa. Birinchisi — ● (SSG): generateStaticParams bilan oldindan yasalgan sahifa belgisi. Ikkinchisi — fayl nomiga -zrbhtn qo'shimchasi: Next dinamik segment ichidagi rasm manziliga xesh qo'shadi. Siz bu manzilni yozmaysiz — metadata'da Next uni o'zi qo'yadi:

text
/blog/portfolio-v2: {"ogImage":"https://example.com/blog/portfolio-v2/opengraph-image-zrbhtn?7a2cc30a9c39bee3","ogType":"article"}

Fayllar: /opengraph-image — 37 328 bayt, post rasmi — 41 196 bayt, ikkalasi PNG 1200×630 (natijalar/30-seo.txt).

4.3 Kamchilik: qalin shrift chiqmadi

Rasmda fontWeight: 700 yozilgan, lekin kanon tekshiruvi ko'rsatdi: sarlavha qalin emas. Sabab: shrift berilmagan — satori o'zining bitta standart shriftini ishlatadi, unda qalin variant yo'q. Brauzer "soxta qalin" chizib bera oladi, satori esa yo'q.

Bu holat 30 dagi ochiq kamchilik. next/font darsida Rubik shriftini qo'shganda OG rasmlarga ham fonts beriladi — va u yerda yangi tuzoq chiqadi (DataView xatosi).

Tekshirib ko'ring: Post OG rasmida display: "grid" bilan ikki ustun qilmoqchisiz. Nima bo'ladi va qanday yechasiz?

Javob

Satori grid'ni qo'llamaydi — hujjat bo'yicha faqat flexbox. Ikki ustunni display: "flex" + flexDirection: "row" va har ustunga width: "50%" (yoki flex: 1) bilan qiling. OG rasm — oddiy tuzilma, flexbox yetarli.

4.4 Twitter rasmi va tekshirish

twitter-image.tsx — xuddi shu konvensiya, faqat twitter:image uchun. Alohida fayl bo'lmasa, X (Twitter) og:image ni oladi — portfolio da shuning uchun bitta rasm yetarli. Rasm faylining o'rni ham muhim: u qaysi papkada tursa, o'sha segment va uning bolalariga qo'llanadi. Root'dagi rasm hamma sahifaga, blog/[slug]/ dagisi faqat postlarga.

Rasmni qanday tekshirasiz? Uch bosqich:

  1. Brauzerda to'g'ridan oching: /opengraph-image — PNG chiqishi kerak.
  2. Sahifa manbasida og:image qatori to'liq manzil bilan (https://…) turganini tekshiring — metadataBase ishlayotganining belgisi.
  3. Sayt internetga chiqqach, Telegram'da o'zingizga havola yuboring. Telegram ko'rinishni keshlaydi: rasmni o'zgartirsangiz, eski ko'rinish bir muddat qolishi mumkin. Rasm manzilidagi ?… qo'shimchasi (opengraph-image?bfd9fbf514465004) — Next qo'yadigan versiya belgisi: rasm o'zgarsa, belgi ham o'zgaradi va botlar yangisini oladi.

5. JSON-LD

5.1 Nima

JSON-LD (JSON Linked Data) — sahifaga qo'yiladigan tuzilgan ma'lumot: "bu sahifa — shu odamniki", "bu — maqola, muallifi va sanasi". Google uni boy natijalar (rich results) uchun o'qiydi. Lug'at — schema.org.

Kanondagi bosh sahifa (holat 30):

tsx
const JSON_LD = {
  "@context": "https://schema.org",
  "@type": "Person",
  name: "Aziz Karimov",
  jobTitle: "Frontend dasturchi",
  url: SAYT_URL,
  sameAs: [`https://github.com/${GITHUB_LOGIN}`, "https://t.me/sizning_kanal"],
};

// <main> ichida:
<script
  type="application/ld+json"
  // XSS: "<" qochiriladi (hujjat: guides/json-ld)
  dangerouslySetInnerHTML={{
    __html: JSON.stringify(JSON_LD).replace(/</g, "\\u003c"),
  }}
/>

sameAs — boshqa joylardagi profillar. t.me/sizning_kanal — o'zingiznikiga almashtiriladigan namuna.

5.2 Nega < ni qochirish kerak

JSON.stringify HTML uchun xavfsiz emas. Agar ma'lumotda </script> satri bo'lsa (masalan blog sarlavhasini foydalanuvchi yozgan bo'lsa), u skript tegini yopib qo'yadi va qolgani HTML sifatida o'qiladi. Natija oynasida ko'ramiz:

js
const malumot = { name: "Ali</script><script>alert(1)</script>" };

const xom = JSON.stringify(malumot);
const xavfsiz = xom.replace(/</g, "\\u003c");

console.log(xom);
const qochirilgan = xavfsiz.split("\\u003c").length - 1;
console.log(qochirilgan); // qochirilgan < soni
console.log(xom.includes("</script>"), xavfsiz.includes("</script>"));
console.log(JSON.parse(xavfsiz).name === malumot.name);

Konsolda:

text
{"name":"Ali</script><script>alert(1)</script>"}
3
true false
true

Ikkinchi qator — nechta < qochirilgani: uchalasi ham. Xavfsiz satrning o'zi (console.log(xavfsiz)) shunday chiqadi: {"name":"Ali\u003c/script>\u003cscript>alert(1)\u003c/script>"}. Biz uni sanoq va true/false bilan ko'rsatdik: natija oynasidagi solishtirish \u003c yozuvini ham < deb o'qiydi, chiqishning o'zi esa xuddi shunday. \u003c — < ning JSON ichidagi yozuvi. Brauzerning HTML tahlilchisi uni teg deb tanimaydi. JSON tahlilchisi esa yana < qilib o'qiydi — oxirgi qator buni tasdiqlaydi: ma'no o'zgarmadi. portfolio dagi ma'lumot o'zimizniki va xavfsiz, lekin qoida doimiy: dangerouslySetInnerHTML ga ketadigan JSON — har doim qochirilgan (XSS darsidagi tamoyil).

6. Hujumchi nigohi

  • robots.txt — xarita, qulf emas. Disallow: /admin/ deb yozish hujumchiga "admin shu yerda" deb aytish bilan teng. Maxfiy yo'llar robots.txt'da emas, auth ortida bo'lsin.
  • JSON-LD orqali XSS. Ma'lumot foydalanuvchidan kelsa (izoh, mahsulot nomi), qochirilmagan JSON.stringify — to'g'ridan skript qo'shish. Yuqoridagi qatorlar buni ko'rsatdi.
  • Dinamik OG rasm — og'ir endpoint. Agar OG rasm har so'rovda chizilsa (ƒ), hujumchi minglab turli ?title= bilan so'rab serverni band qiladi. portfolio da rasmlar build'da tayyor (○/●) — bu xavf yo'q.

7. Portfolio qadami: SEO fayllari va commit

7.1 Fayllar (holat 30)

Oldingi darsdagi metadata'ga qo'shiladi:

Fayl Natija
app/sitemap.ts /sitemap.xml — 11 URL
app/robots.ts /robots.txt — /api/ yopiq, sitemap havolasi
app/manifest.ts /manifest.webmanifest
app/opengraph-image.tsx /opengraph-image — sayt rasmi
(sayt)/blog/[slug]/opengraph-image.tsx har post rasmi (●)
(sayt)/page.tsx JSON-LD Person

7.2 Tekshiruv

bash
npm run lint
npm run tip
npm run build
npm run start

Brauzerda /sitemap.xml, /robots.txt, /opengraph-image ni oching. Kanon o'lchovi (holat 30): 9 sahifada title shablon bo'yicha, canonical o'ziniki, og:image — root yoki o'z rasmi; JS 145.6 → 146.5 KB gzip (+0.9 — metadata uchun), axe 0, hydration 0.

7.3 Commit

bash
git add -A
git commit

Commit (kanon): SEO: title shabloni, canonical, OG/Twitter, viewport; sitemap, robots, manifest, OG rasmlar, JSON-LD.

Deploy'dan keyin (sayt internetda bo'lganda) Google Search Console'ga sitemap manzilini qo'shasiz — bu deploy darsida.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Yangi post sitemap'da yo'q public/sitemap.xml qo'lda app/sitemap.ts — ma'lumotdan
Sitemap'da localhost yoki example.com SAYT_URL o'zgartirilmagan, build eski To'g'ri SAYT_URL bilan qayta build
OG rasm build xatosi: grid satori display: grid ni bilmaydi flexbox
OG rasmda matn qalin emas shrift berilmagan fonts (ttf/otf/woff) — next/font darsida
public/ dagi robots.txt va app/robots.ts ziddiyati ikki manba bittasini qoldiring
JSON-LD orqali sahifa buzildi JSON.stringify qochirilmagan .replace(/</g, "\\u003c")
OG rasm har so'rovda chiziladi (ƒ) keshlanmagan ma'lumot generateStaticParams, "use cache"

9. Mashqlar

Mashqlar kurs/mashqlar/23/30-seo-fayllar/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.

1-mashq (oson): fayl nomi → manzil

  • app/sitemap.ts →
  • app/robots.ts →
  • app/manifest.ts →
  • app/opengraph-image.tsx →
Yechim

Fayl nomi — konvensiya: Next kengaytmasiz nomdan standart manzilni yasaydi. /sitemap.xml va /robots.txt — qidiruv tizimlari aynan shu manzillarni qidiradi. /opengraph-image manzilini siz yozmaysiz — Next uni og:image ga o'zi qo'yadi (dinamik segmentda xesh bilan).

2-mashq (o'rta): sitemap'dan XML

«sitemap mantiqi» blokidagi royxat ni haqiqiy XML satriga aylantiring: har element <url><loc>…</loc></url>, lastModified bo'lsa — <lastmod>…</lastmod> ham. Boshiga <urlset>, oxiriga </urlset>. Satrlarni map va join bilan yig'ing.

Yechim
js
const SAYT = "https://example.com";
const royxat = [
  { url: SAYT },
  { url: `${SAYT}/loyihalar/vazifalar`, lastModified: "2026-10-01" },
];

function xmlYasa(royxat) {
  const urllar = royxat.map((s) => {
    const lm = s.lastModified
      ? `<lastmod>${s.lastModified}</lastmod>`
      : "";
    return `<url><loc>${s.url}</loc>${lm}</url>`;
  });
  return ["<urlset>", ...urllar, "</urlset>"].join("\n");
}

console.log(xmlYasa(royxat));

Konsolda:

text
<urlset>
<url><loc>https://example.com</loc></url>
<url><loc>https://example.com/loyihalar/vazifalar</loc><lastmod>2026-10-01</lastmod></url>
</urlset>

Haqiqiy sitemap'da urlset da xmlns atributi ham bor va <loc> dagi & kabi belgilar qochiriladi — Next buni o'zi qiladi. Shuning uchun haqiqiy loyihada XML'ni qo'lda yasamaysiz.

3-mashq (qiyin): xavfsiz JSON-LD

jsonLdYoz(obyekt) funksiyasini yozing: <, > va & ni mos ravishda \u003c, \u003e, \u0026 ga almashtirsin (qo'shimcha ehtiyot). Keyin uch tekshiruv: natijada </script> yo'q, JSON.parse asl obyektni qaytaradi, & ham saqlanadi. replace funksiya bilan — satrni o'zgartirish.

Yechim
js
const ALMASHTIR = { "<": "\\u003c", ">": "\\u003e", "&": "\\u0026" };

function jsonLdYoz(obyekt) {
  const satr = JSON.stringify(obyekt);
  return satr.replace(/[<>&]/g, (b) => ALMASHTIR[b]);
}

const post = { headline: "React & Next: </script> tuzog'i" };
const satr = jsonLdYoz(post);

console.log(satr.includes("\\u0026"), satr.includes("\\u003e"));
console.log(satr.includes("</script>"));
console.log(JSON.parse(satr).headline === post.headline);

Konsolda:

text
true true
false
true

& ni qochirish — HTML entity'lar (&lt;) bilan chalkashlikning oldini oladi. Next hujjati minimal yechim sifatida faqat < ni qochirishni ko'rsatadi; uchala belgini qochirish — keng tarqalgan qat'iyroq variant (masalan serialize-javascript kutubxonasi ham shunday qiladi).

4-mashq: Portfolio qadami

O'z portfolio ingizga sitemap.ts, robots.ts, manifest.ts, ikki opengraph-image.tsx va bosh sahifaga JSON-LD qo'shing. Build jadvalida ○ /sitemap.xml va ● belgili post rasmlarini toping. /opengraph-image ni brauzerda ochib, rasmni ko'ring — sarlavha qalinmi? Keyin oldingi dars metadata'si bilan birga commit qiling.

Yechim

Kutilgan build qatorlari — «Har post uchun rasm» bo'limidagi jadval kabi (xesh boshqa bo'lishi mumkin). Rasmda sarlavha qalin emas — bu kanondagi ochiq kamchilik, next/font darsida tuzatiladi. Commit — kanon xabari bilan yoki o'zingizniki.

10. Real ishda

  • Har kontent saytida (blog, do'kon, yangiliklar) sitemap.ts ma'lumotlar bazasidan yasaladi; katta saytlarda — generateSitemaps bilan bo'laklarga.
  • Dinamik OG rasmlar — Vercel, GitHub, dev.to: har maqola yoki repo uchun avtomatik rasm. Telegram kanaliga post ulashganda chiroyli karta chiqadi — bosilish (CTR) oshadi.
  • JSON-LD — e-commerce (Product, narx, reyting), retseptlar (Recipe), tadbirlar: Google natijasida yulduzcha va narx ko'rinadi. Tekshirish — Google Rich Results Test.
  • Intervyu savoli: "Next'da sitemap qanday yasaladi?", "OG rasmni qanday avtomatlashtirasiz?" — sitemap.ts va opengraph-image.tsx ni bilish va satori cheklovlarini aytish yetarli.

Xulosa

  • app/ dagi maxsus fayllar SEO fayllariga aylanadi: sitemap.ts, robots.ts, manifest.ts, icon.*, opengraph-image.tsx.
  • Sitemap ma'lumotdan yasaladi (11 URL), postlar keshiga bog'lanib ○ 30d 1y; 50 000 dan ko'p URL — generateSitemaps.
  • robots.txt — iltimos, himoya emas; /api/ ni handler o'zi himoya qiladi.
  • OG rasm — satori: faqat flexbox, ttf/otf/woff, 500 KB; dinamik segmentda generateStaticParams bilan ● va xeshli manzil.
  • JSON-LD < qochirilib yoziladi — aks holda </script> orqali XSS.

Keyingi dars: next/image — loyiha rasmini avtomatik o'lchamlash, LCP uchun preload va proxy matcher topgan xatoning to'liq hikoyasi.

Manbalar

  • Next.js hujjati (16.4.0, node_modules/next/dist/docs/): sitemap.xml (generateSitemaps), robots.txt, manifest.json, opengraph-image, favicon, icon, and apple-icon, ImageResponse, "JSON-LD" guide — nextjs.org/docs/app/api-reference/file-conventions/metadata (2026-10-21 da o'qildi)
  • Kanon natijalari: natijalar/30-seo.txt, natijalar/tekshiruv-30.txt, natijalar/tajriba/30-og_*.png; holat 30 diff
  • Google: "Build and submit a sitemap" (50 000 URL chegarasi) — developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap
  • schema.org — Person
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Next.js SEO fayllari: sitemap.ts, robots.ts, manifest va OG rasmlar — IlmHamroh