IlmHamroh
JavaScript Full-stack/23-qism. Next.js24/43-dars19 daqiqa
Mundarija (38)

Next.js Route Handlers: route.ts bilan API, RSS va webhook

Qisqacha: Route Handler — app/ ichidagi route.ts fayli: u sahifa (HTML) emas, istalgan javob qaytaradi — JSON, XML, matn, fayl. Faylda HTTP metod nomidagi funksiyalar eksport qilinadi (GET, POST, PUT, DELETE, OPTIONS …), ular Web standart Request ni oladi va Response qaytaradi: return Response.json({ ok: true }). Dinamik segment ([slug]) qiymati ikkinchi argumentda — await ctx.params. Eksport qilinmagan metodga Next.js 405 qaytaradi. Cache Components'da request ni o'qimaydigan GET ham build vaqtida tayyorlanadi (○), request ni o'qisa — har so'rovda (ƒ). Tashqi dasturlar (RSS o'quvchi, to'lov tizimi webhook'i, mobil ilova) uchun aynan shu kerak.

Bu darsda

  • route.ts yozasiz: GET va POST, Response.json, status va sarlavhalar.
  • NextRequest bilan query parametrlarni o'qiysiz va dinamik [slug] handler yozasiz.
  • CORS sarlavhalari va OPTIONS so'rovini, oqimli javobni haqiqiy natija bilan ko'rasiz.
  • To'lov tizimi webhook'ini imzo (HMAC) bilan qabul qilishni o'rganasiz.
  • portfolio blogiga RSS lenta (/blog/rss.xml) qo'shib, holat 24 ni commit qilasiz.

Oldin bilishingiz kerak: Serverda ma'lumot olish, Cache Components II (birinchi route.ts), Request, Response, Headers, CORS mijoz tomondan, Server Actions xavfsizligi.

1. Nega bu kerak?

Hozirgacha portfolio faqat odamlar uchun ishladi: brauzer HTML so'raydi, Next.js sahifa beradi. Server Action ham — sizning formangiz uchun.

Lekin saytingiz bilan dasturlar ham gaplashmoqchi:

  • RSS o'quvchi (Feedly, Telegram'dagi RSS botlar) — "yangi postlar bormi?" deb /blog/rss.xml ni so'raydi. Unga HTML emas, XML kerak.
  • To'lov tizimi (Click, Payme) — "mijoz to'ladi" degan xabarni sizning manzilingizga POST qiladi.
  • Mobil ilova — JSON so'raydi.
  • Cron — kechasi keshni yangilashni so'raydi (kanondagi /api/qayta-yangila).

Ularning birortasi ham forma bosmaydi va React komponentini o'qimaydi. Ularga oddiy HTTP manzil kerak: aniq URL, aniq metod, aniq format.

Restoranni tasavvur qiling. Zal — mehmonlar uchun (sahifalar). Orqa eshik — yetkazib beruvchilar uchun: ular zalga kirmaydi, faqat yukni topshiradi va hujjatga imzo qo'yadi. Route Handler — saytingizning orqa eshigi.

Cache Components II darsida bitta Route Handler'ni ko'rdingiz — /api/qayta-yangila. Bugun ularni to'liq o'rganamiz.

2. Birinchi route.ts

2.1 Fayl va metodlar

Route Handler — papkadagi route.ts fayli. Manzil page.tsx dagidek papka yo'lidan olinadi:

ts
// app/api/salom/route.ts → GET /api/salom
export function GET() {
  return Response.json({ salom: "dunyo" });
}
Qoida Izoh
Fayl nomi — route.ts page.tsx o'rnida; HTML emas, Response
Funksiya nomi — HTTP metod GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS (katta harf)
Qaytaradi — Response Web standart klass (W-31)
Yo'q metod Next.js o'zi 405 Method Not Allowed

api/ papkasi shart emas — bu shunchaki kelishuv. Kanondagi RSS lenta app/blog/rss.xml/route.ts da: papka nomida nuqta bo'lishi mumkin, URL — /blog/rss.xml.

2.2 Biz topdik: bitta papkada page.tsx va route.ts

Bitta manzil bir vaqtda sahifa ham, API ham bo'la olmaydi. Biz kanon nusxasida bitta papkaga ikkalasini qo'yib build qildik:

text
Error: An issue occurred while preparing your Next.js app
Conflicting route and page at /api/loyihalar-ikki: route at /api/loyihalar-ikki/route and page at /api/loyihalar-ikki/page

Tarjimasi: "/api/loyihalar-ikki da marshrut va sahifa to'qnashdi". Lekin turli papkalarda bemalol yashaydi: kanonda (sayt)/blog/page.tsx (sahifa /blog) va blog/rss.xml/route.ts (lenta /blog/rss.xml). Route group (sayt) URL'ga ta'sir qilmaydi (Route groups), shuning uchun ikkala blog papkasi bitta URL daraxtiga tushadi.

2.3 Response — Node'da ham, brauzerda ham

Response — fetch qaytaradigan o'sha klass. Siz uni fetch darslarida "o'qigan" edingiz, endi "yasaysiz". U Node 24 da ham bor — Route Handler'siz sinash mumkin:

js
const ok = Response.json(
  { slug: "bahor", nomi: "Choyxona «Bahor» sayti" },
  { headers: { "Cache-Control": "no-store" } },
);
console.log(ok.status, ok.headers.get("content-type"));
console.log(await ok.json());

const notFound = Response.json(
  { xato: "topilmadi" },
  { status: 404 },
);
console.log(notFound.status, notFound.ok);

const xml = new Response("<rss/>", {
  headers: { "Content-Type": "application/rss+xml; charset=utf-8" },
});
console.log(xml.headers.get("content-type"), await xml.text());

Konsolda:

text
200 application/json
{ slug: 'bahor', nomi: 'Choyxona «Bahor» sayti' }
404 false
application/rss+xml; charset=utf-8 <rss/>

Response.json(…) ikki ishni qiladi: obyektni JSON'ga aylantiradi va Content-Type: application/json ni qo'yadi. Ikkinchi argument — status va sarlavhalar. Boshqa format (XML, matn) uchun — new Response(matn, { headers }) va Content-Type ni o'zingiz yozasiz.

Tekshirib ko'ring: app/api/vaqt/route.ts da faqat GET bor. Mobil ilova PUT /api/vaqt yubordi. Nima bo'ladi va bu xatoni kim qaytaradi?

Javob

405 Method Not Allowed — uni Next.js o'zi qaytaradi, sizning kodingiz ishlamaydi. PUT kerak bo'lsa, faylda export async function PUT(request: Request) yozasiz.

3. So'rovni o'qish

3.1 Request va NextRequest

Handler birinchi argument sifatida so'rovni oladi. Ikki turdan biri:

Tur Qayerdan Qo'shimcha
Request Web standart url, method, headers, json(), text(), formData()
NextRequest next/server + nextUrl (tayyor URL: searchParams, pathname), cookies

Biz kanon nusxasida tajriba uchun GET /api/loyihalar?texnologiya=React handler yozdik (kanonga kirmagan):

ts
import type { NextRequest } from "next/server";
import { LOYIHALAR } from "@/malumot/loyihalar";

const CORS = {
  "Access-Control-Allow-Origin": "https://example.com",
  "Access-Control-Allow-Methods": "GET, OPTIONS",
};

export function GET(request: NextRequest) {
  const texnologiya =
    request.nextUrl.searchParams.get("texnologiya");
  const royxat = LOYIHALAR.filter(
    (l) => !texnologiya || l.texnologiyalar.includes(texnologiya),
  ).map((l) => ({ slug: l.slug, nomi: l.nomi }));
  return Response.json(royxat, { headers: CORS });
}

export function OPTIONS() {
  return new Response(null, { status: 204, headers: CORS });
}

Natija (next start, curl -i):

text
HTTP/1.1 200 OK
access-control-allow-methods: GET, OPTIONS
access-control-allow-origin: https://example.com
content-type: application/json
[{"slug":"vazifalar","nomi":"Vazifalar ilovasi"}]

Javobda faqat ikki maydon — slug va nomi. LOYIHALAR dagi repo, case study va boshqalar chiqmadi: DAL darsidagi DTO qoidasi API'da ham amal qiladi.

Sarlavhalar (request.headers.get("authorization")) va cookie'lar (request.cookies.get("til")) ham shu request dan o'qiladi. Funksiya ko'rinishidagi cookies() va headers() (next/headers) ham ishlaydi — Request API'lar darsida.

3.2 Dinamik segment: params

app/api/loyihalar/[slug]/route.ts — har loyiha uchun alohida manzil. Qiymat ikkinchi argumentda keladi va sahifadagidek Promise (Fayl-asosli routing):

ts
import { LOYIHALAR } from "@/malumot/loyihalar";

export async function GET(
  _request: Request,
  ctx: RouteContext<"/api/loyihalar/[slug]">,
) {
  const { slug } = await ctx.params;
  const l = LOYIHALAR.find((x) => x.slug === slug);
  if (!l) {
    return Response.json({ xato: "topilmadi" }, { status: 404 });
  }
  return Response.json({ slug: l.slug, nomi: l.nomi });
}

RouteContext<"/api/loyihalar/[slug]"> — PageProps ning Route Handler'dagi juftligi, global tur (next typegen yaratadi). Biz next typegen dan oldin tsc ni ishga tushirganimizda tur hali yo'q edi:

text
src/app/api/loyihalar/[slug]/route.ts(6,21): error TS2344: Type '"/api/loyihalar/[slug]"' does not satisfy the constraint 'AppRouteHandlerRoutes'.

next typegen dan keyin — 0 xato. Kanon skripti shuning uchun "tip": "next typegen && tsc --noEmit" (Loyiha yaratish).

Natijalar:

text
== GET /api/loyihalar/bahor
{"slug":"bahor","nomi":"Choyxona «Bahor» sayti"}
== GET /api/loyihalar/yoq
{"xato":"topilmadi"} [404]
== POST /api/loyihalar
405

E'tibor bering: sahifadan farqli o'laroq, Route Handler'da 404 ni bemalol qaytarasiz — oqim yo'q, status birinchi baytdan oldin aniq (Streaming darsidagi muammo bu yerda yo'q).

3.3 Tana: json(), text(), formData()

POST da so'rov tanasi — await request.json() (JSON), await request.formData() (forma), await request.text() (xom matn). Tana faqat bir marta o'qiladi: json() dan keyin text() — xato. Kelgan JSON ham ishonchsiz: Server Actions II dagi kabi Zod bilan tekshiring.

4. Cache Components'da Route Handler

4.1 ○ yoki ƒ?

Route Handler ham sahifa kabi build vaqtida tayyorlanishi mumkin. Biz qo'shgan handlerlar bilan build jadvali:

text
├ ƒ /api/loyihalar
├ ƒ /api/loyihalar/[slug]
├ ƒ /api/oqim
├ ƒ /api/qayta-yangila
├ ƒ /api/webhook
├ ○ /blog/rss.xml                          30d      1y

Qoida:

Handler Belgi Sabab
GET — request ni o'qimaydi, ma'lumot keshlangan ○ Build vaqtida bir marta (kanon RSS)
GET — request.nextUrl, params, cookies o'qiydi ƒ Javob so'rovga bog'liq
GET — connection() chaqiradi ƒ "Har so'rovda" belgisi
POST, PUT, DELETE … ƒ Doim so'rov paytida

Kanon RSS lentasi ○ — u postlar() ni o'qiydi, postlar() esa "use cache" + cacheLife("max") (Cache Components I). Javob sarlavhasi buni tasdiqlaydi (kanon natijalar/24-rss.txt):

text
status 200 | application/rss+xml; charset=utf-8 | s-maxage=2592000, stale-while-revalidate=28944000 | 1434 belgi

s-maxage=2592000 — 30 kun (soniyalarda). RSS o'quvchilar soatiga yuz marta so'rasa ham, server XML'ni qayta yasamaydi.

4.2 Argumentli new Date() ruxsat

Kanon RSS'da sana shunday yoziladi:

ts
<pubDate>${new Date(`${p.sana}T00:00:00Z`).toUTCString()}</pubDate>

Hydration darsida argumentsiz new Date() prerender'da build xatosi bergan edi ("unstable value"). Bu yerda esa argument bor — natija har doim bir xil, shuning uchun ruxsat. toUTCString() — RSS talab qiladigan RFC 822 formati: Wed, 21 Oct 2026 00:00:00 GMT.

Tekshirib ko'ring: RSS handler'ga ?limit=5 parametrini qo'shib, request.nextUrl.searchParams dan o'qisak, build jadvalidagi belgi qanday o'zgaradi va bu qanday narx?

Javob

○ dan ƒ ga: javob endi so'rovga bog'liq, build vaqtida bir marta tayyorlab bo'lmaydi. Har RSS so'rovi serverda XML'ni qayta yasaydi va CDN s-maxage bilan saqlay olmaydi. Postlar "use cache" da qolgani uchun fayl o'qilmaydi, lekin javob har safar yig'iladi.

5. Kanon: RSS lenta

5.1 XML qochirish

RSS — XML. Post sarlavhasida <, & yoki qo'shtirnoq bo'lsa, XML buziladi (yoki boshqa teg "kiritiladi"). Kanon har matnni maxsus belgilardan tozalaydi:

js
function xml(matn) {
  return matn.replace(
    /[<>&'"]/g,
    (b) =>
      ({
        "<": "&lt;",
        ">": "&gt;",
        "&": "&amp;",
        "'": "&apos;",
        '"': "&quot;",
      })[b] ?? b,
  );
}

console.log(xml("Portfolio'ni Next.js'da qayta qurdim"));
console.log(xml('<script>alert("x")</script> & boshqalar'));

Konsolda:

text
Portfolio&apos;ni Next.js&apos;da qayta qurdim
&lt;script&gt;alert(&quot;x&quot;)&lt;/script&gt; &amp; boshqalar

Bu XSS darsidagi HTML qochirishning XML'dagi egizagi. O'zbekcha apostrof ham qochiriladi (&apos;) — XML o'quvchi uni yana ' qilib ko'rsatadi. Kanondagi funksiya TypeScript'da (matn: string); bu yerda tur izohlarisiz.

5.2 To'liq handler

ts
import { postlar } from "@/malumot/blog";
import { SAYT_URL } from "@/malumot/sozlama";

// Route Handler (N-24): /blog/rss.xml — Web standart Response.
// Cache Components'da GET ham sahifadek prerender qilinadi
// (postlar() "use cache" ortida — build vaqtida tayyor XML).
// function xml(…) — yuqoridagi

export async function GET() {
  const royxat = await postlar();
  const elementlar = royxat
    .map(
      (p) => `    <item>
      <title>${xml(p.sarlavha)}</title>
      <link>${SAYT_URL}/blog/${p.slug}</link>
      <guid>${SAYT_URL}/blog/${p.slug}</guid>
      <pubDate>${new Date(`${p.sana}T00:00:00Z`).toUTCString()}</pubDate>
      <description>${xml(p.tavsif)}</description>
    </item>`,
    )
    .join("\n");
  const tana = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Aziz Karimov — blog</title>
    <link>${SAYT_URL}/blog</link>
    <description>O'rganganlarim va loyihalardagi saboqlar</description>
    <language>uz</language>
${elementlar}
  </channel>
</rss>
`;
  return new Response(tana, {
    headers: { "Content-Type": "application/rss+xml; charset=utf-8" },
  });
}

Asosiy qismlari:

Qism Ma'nosi
postlar() Ro'yxat — DAL emas, blog modulidan (DAL bitta yozuvli sahifalar uchun)
SAYT_URL sozlama.ts dagi https://example.com — deploy'dan keyin o'zingiznikiga
<guid> Post identifikatori — RSS o'quvchi takrorni shu bilan aniqlaydi
<language>uz</language> Lenta tili
Content-Type application/rss+xml — brauzer va o'quvchilar formatni taniydi

SAYT_URL uchun yangi qator src/malumot/sozlama.ts ga qo'shildi:

ts
// Saytning to'liq manzili (RSS, sitemap, canonical uchun).
// Deploy'dan keyin o'zingiznikiga almashtiring.
export const SAYT_URL = "https://example.com";

5.3 Kanon tekshiruvi

Kanon lentani haqiqiy Chrome'ning DOMParser i bilan o'qib tekshirdi (natijalar/24-rss.txt):

text
XML tahlil: {"xato":null,"item":3,"sarlavha":"Portfolio'ni Next.js'da qayta qurdim","sana":"Wed, 21 Oct 2026 00:00:00 GMT"}
POST: 405

parsererror yo'q (XML to'g'ri), 3 ta item, sarlavha qochirishdan keyin to'g'ri tiklandi. POST — 405, chunki faylda faqat GET bor.

flowchart LR
  O["RSS o'quvchi"] -->|"GET /blog/rss.xml"| H["route.ts: GET"]
  H --> P["postlar(): use cache, max"]
  P --> X["XML + Content-Type"]
  X -->|"200, s-maxage 30 kun"| O

Diagrammadagi hamma qadam build vaqtida bir marta bajarilgan — so'rov kelganda tayyor XML beriladi.

6. Webhook'ni qabul qilish

6.1 Muammo: kim yubordi?

To'lov tizimi (Click, Payme va boshqalar) mijoz to'laganda sizning manzilingizga POST yuboradi: "buyurtma 17, 35 000 so'm, to'landi". Bu webhook — tashqi xizmat sizga o'zi qo'ng'iroq qiladi.

Xavf aniq: manzil ochiq. Hujumchi o'zi "to'landi" deb POST yuborsa, siz bepul buyurtma berib yuborasiz. Shuning uchun har xizmat so'rovni imzolaydi: tana va umumiy sir bilan HMAC (Hash-based Message Authentication Code — kalitli xesh) hisoblanadi va sarlavhada yuboriladi. Siz ham xuddi shunday hisoblaysiz va solishtirasiz. Sirni bilmagan hujumchi to'g'ri imzo yasay olmaydi.

Har to'lov tizimining aniq formati (qaysi sarlavha, qaysi algoritm, qaysi maydonlar) — o'z hujjatida. Kanonda to'lov tizimi hisobi yo'q; haqiqiy integratsiya — 29-qismda. Quyidagi — umumiy naqsh, biz kanon nusxasida soxta webhook bilan sinadik.

6.2 Biz sinadik: imzoli webhook

ts
import { createHmac, timingSafeEqual } from "node:crypto";

// TAJRIBA (kanon emas): imzoli webhook — tana HMAC-SHA256 bilan
export async function POST(request: Request) {
  const tana = await request.text(); // xom tana — imzo shundan
  const kelgan = request.headers.get("x-imzo") ?? "";
  const siri = process.env.WEBHOOK_SIRI ?? "";
  const kutilgan = createHmac("sha256", siri)
    .update(tana)
    .digest("hex");
  const a = Buffer.from(kelgan);
  const b = Buffer.from(kutilgan);
  if (a.length !== b.length || !timingSafeEqual(a, b))
    return Response.json({ xato: "imzo noto'g'ri" }, { status: 401 });
  const hodisa = JSON.parse(tana) as { tur?: string };
  return Response.json({ qabul: hodisa.tur ?? "?" });
}

Muhim detal: imzo xom matndan hisoblanadi — request.text(). request.json() qilib, keyin JSON.stringify bilan qayta yasasangiz, bo'shliqlar yoki maydonlar tartibi o'zgarishi mumkin — imzo mos kelmaydi. timingSafeEqual — Cache Components II dagi vaqt hujumidan himoya.

Uchta so'rov (curl, sir sinov-siri, tana {"tur":"tolov.tasdiqlandi","summa":35000}):

text
== webhook to'g'ri imzo
{"qabul":"tolov.tasdiqlandi"} [200]
== webhook soxta imzo
{"xato":"imzo noto'g'ri"} [401]
== webhook tana o'zgartirilgan
{"xato":"imzo noto'g'ri"} [401]

Uchinchisi eng muhim: hujumchi haqiqiy so'rovni ushlab, summa ni 35000 dan 1 ga o'zgartirdi va eski imzoni qoldirdi. Imzo endi tanaga mos emas — rad etildi.

6.3 Natija oynasida: imzoni hisoblash

HMAC'ni Node'da o'zingiz hisoblang:

js
import { createHmac } from "node:crypto";

const secret = "sinov-siri";
const sign = (body) =>
  createHmac("sha256", secret).update(body).digest("hex");

const body = '{"tur":"tolov.tasdiqlandi","summa":35000}';
const forged = '{"tur":"tolov.tasdiqlandi","summa":1}';

console.log(sign(body).length); // 64
console.log(sign(body) === sign(body)); // true
console.log(sign(body) === sign(forged)); // false

Imzo — 64 belgili hex satr (SHA-256 = 32 bayt). Bir xil tana — bir xil imzo; bitta raqam o'zgarsa — butunlay boshqa imzo. HMAC chuqur — crypto: hash va HMAC (24-qism). node:crypto brauzerda yo'q — bu blok faqat Node'da tekshiriladi.

6.4 Webhook qoidalari

Qoida Nega
Imzoni tekshirish — birinchi ish Ochiq manzil
Tez javob (200) qaytarish Xizmat javobni bir necha soniya kutadi, keyin qayta yuboradi
Bir hodisa ikki marta kelishi mumkin Hodisa ID'sini saqlab, takrorni o'tkazib yuboring (idempotentlik)
Summani o'zingizdagi buyurtma bilan solishtirish Imzo to'g'ri bo'lsa ham, buyurtmani serverdan o'qing
Sir — env'da WEBHOOK_SIRI, NEXT_PUBLIC_ siz (N-39)

Og'ir ishni (email yuborish) javobdan keyinga qoldirish uchun Next.js'da after() bor — Request API'lar darsida.

Tekshirib ko'ring: hujumchi haqiqiy webhook so'rovini ushlab, uni o'zgartirmasdan 10 marta qayta yubordi. Imzo tekshiruvi uni to'xtatadimi?

Javob

Yo'q — tana ham, imzo ham haqiqiy, imzo to'g'ri chiqadi. Qayta yuborishga (replay) qarshi boshqa qavat kerak: hodisa ID'sini saqlab takrorni o'tkazib yuborish (idempotentlik) va ko'p xizmatlarda imzoga kiritilgan vaqt belgisini tekshirish. 3-mashq aynan shu haqda.

7. CORS va oqimli javob

7.1 CORS

Boshqa domendagi sayt (masalan, https://example.com dagi blog) brauzerdan sizning API'ingizga fetch qilsa, brauzer javobni faqat ruxsat bo'lsa beradi — bu CORS. Ruxsat — javob sarlavhalarida. «Request va NextRequest» bo'limidagi handler aynan shuni qiladi. Murakkab so'rovlardan oldin brauzer preflight — OPTIONS yuboradi:

text
== OPTIONS
HTTP/1.1 204 No Content
access-control-allow-methods: GET, OPTIONS
access-control-allow-origin: https://example.com

Qoidalar:

  • Access-Control-Allow-Origin: * — "hamma sayt" — faqat ochiq, sirsiz ma'lumot uchun (masalan ochiq loyihalar ro'yxati).
  • Cookie bilan ishlaydigan API'da * ishlamaydi — aniq domen kerak.
  • CORS — brauzer qoidasi. curl va server uni e'tiborga olmaydi. CORS himoya emas: u boshqa saytning JS'i javobni o'qishini cheklaydi, so'rovni emas.

O'z saytingizdagi fetch (bir xil domen) uchun CORS umuman kerak emas.

7.2 Oqimli javob

Response tanasi tayyor satr bo'lishi shart emas — ReadableStream ham bo'ladi (oqimlar). AI chat javoblari, katta eksport fayllari shunday yuboriladi. Biz sinagan handler:

ts
import { connection } from "next/server";

export async function GET() {
  await connection(); // har so'rovda — build'da emas
  const enc = new TextEncoder();
  const oqim = new ReadableStream({
    async start(c) {
      for (const soz of ["birinchi", "ikkinchi", "uchinchi"]) {
        c.enqueue(enc.encode(soz + "\n"));
        await new Promise((r) => setTimeout(r, 300));
      }
      c.close();
    },
  });
  return new Response(oqim, {
    headers: { "Content-Type": "text/plain; charset=utf-8" },
  });
}

Node fetch bilan bo'lak-bo'lak o'qilgani (vaqt 100 ms ga yaxlitlangan, birinchi so'rov — sovuq server):

text
200 ms: "birinchi\n"
400 ms: "ikkinchi\n"
800 ms: "uchinchi\n"

Javob bitta, lekin bo'laklar kelishi bilan o'qildi. connection() siz bu GET build vaqtida bir marta ishlab, tayyor matn bo'lib qolardi.

8. Portfolio qadami: RSS va commit

8.1 Fayllar

Fayl O'zgarish
src/app/blog/rss.xml/route.ts Yangi: GET, xml() qochirish, postlar(), RSS 2.0
src/malumot/sozlama.ts SAYT_URL = "https://example.com"

Papka (sayt) dan tashqarida: app/blog/rss.xml/. U (sayt)/blog/ bilan bitta /blog daraxtida yashaydi.

Holatdagi ikkinchi Route Handler — /api/qayta-yangila (Cache Components II). Lenta havolasi (<link rel="alternate" type="application/rss+xml">) sahifa <head> iga Metadata API darsida qo'shiladi.

8.2 Tekshiruv

  1. npm run build — jadvalda ○ /blog/rss.xml 30d 1y.
  2. npm run start, brauzerda /blog/rss.xml — XML ko'rinadi.
  3. curl -i http://localhost:3000/blog/rss.xml — content-type: application/rss+xml; charset=utf-8.
  4. curl -X POST …/blog/rss.xml — 405.

Kanon: lint, tip, format — 0; JS o'zgarmadi (handler butunlay serverda); axe 0, hydration 0.

8.3 Commit

bash
git add -A
git commit

Commit (kanon): Blog: RSS lenta — /blog/rss.xml Route Handler.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Conflicting route and page at … route.ts va page.tsx bitta papkada Turli papkalar
405 Method Not Allowed Bu metod eksport qilinmagan export function POST…
TS2344 … 'AppRouteHandlerRoutes' next typegen ishlamagan next typegen && tsc
params.slug — undefined await ctx.params unutilgan const { slug } = await ctx.params
Body is unusable Tana ikki marta o'qilgan Bir marta: text() yoki json()
Webhook imzosi doim noto'g'ri Imzo qayta yasalgan JSON'dan hisoblangan Xom request.text() dan
Boshqa saytdan fetch — CORS xatosi Access-Control-Allow-Origin yo'q Sarlavha va OPTIONS
RSS'da buzilgan XML Sarlavhadagi & yoki < qochirilmagan xml() funksiyasi
GET javobi build'dan beri o'zgarmaydi request o'qilmaydi — ○ Kerak bo'lsa connection() yoki kesh yangilash

10. Mashqlar

Mashqlar kurs/mashqlar/23/24-route-handlers/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.

1-mashq (oson): manzil va belgi

  • app/api/ob-havo/route.ts dagi GET → URL:
  • Faylda faqat GET, so'rov DELETE → status:
  • GET request.nextUrl.searchParams ni o'qiydi → build belgisi:
  • GET faqat "use cache" li ma'lumotni qaytaradi → build belgisi:
Yechim
  • /api/ob-havo — papka yo'li, route.ts siz.
  • 405 — Next.js o'zi qaytaradi.
  • ƒ — javob so'rovga bog'liq.
  • ○ — build vaqtida tayyor (kanon RSS kabi).

2-mashq (o'rta): JSON API handler

Route Handler'siz, oddiy funksiya sifatida handleGet(url) yozing: url — to'liq manzil satri. ?texnologiya= bo'yicha loyihalarni filtrlab, Response.json qaytarsin; noma'lum texnologiya bo'lsa — bo'sh massiv va status 200; texnologiya 30 belgidan uzun bo'lsa — 400 va { xato: "juda uzun" }. Uch so'rov bilan sinang va status hamda tanani chiqaring.

Yechim
js
const PROJECTS = [
  { slug: "vazifalar", nomi: "Vazifalar ilovasi", tex: ["React"] },
  { slug: "bahor", nomi: "«Bahor» sayti", tex: ["HTML", "CSS"] },
];

function handleGet(url) {
  const tex = new URL(url).searchParams.get("texnologiya");
  if (tex && tex.length > 30) {
    return Response.json({ xato: "juda uzun" }, { status: 400 });
  }
  const list = PROJECTS.filter((p) => !tex || p.tex.includes(tex));
  const body = list.map(({ slug, nomi }) => ({ slug, nomi }));
  return Response.json(body);
}

for (const q of ["React", "Vue", "x".repeat(40)]) {
  const url = `https://example.com/api/loyihalar?texnologiya=${q}`;
  const res = handleGet(url);
  console.log(res.status, JSON.stringify(await res.json()));
}

Konsolda:

text
200 [{"slug":"vazifalar","nomi":"Vazifalar ilovasi"}]
200 []
400 {"xato":"juda uzun"}

Haqiqiy handler'da new URL(url) o'rniga request.nextUrl bo'ladi. Bo'sh natija — xato emas (200 va []); noto'g'ri kiritish — 400. API foydalanuvchisi (mobil ilova) shu farqqa qarab qaror qiladi.

3-mashq (qiyin): webhook takrori

To'lov tizimi bitta hodisani ikki marta yuborishi mumkin. handleWebhook(body, signature, seen) funksiyasini yozing: imzo noto'g'ri — 401; body dagi id seen (Set) da bor — 200 va { takror: true } (qayta ishlamaymiz); aks holda id ni seen ga qo'shib — 200 va { qabul: id }. Imzo — HMAC-SHA256, sir sinov-siri. Bir xil hodisani ikki marta va soxta imzo bilan bir marta yuboring.

Yechim
js
import { createHmac, timingSafeEqual } from "node:crypto";

const SECRET = "sinov-siri";
const sign = (body) =>
  createHmac("sha256", SECRET).update(body).digest("hex");

function handleWebhook(body, signature, seen) {
  const a = Buffer.from(signature);
  const b = Buffer.from(sign(body));
  if (a.length !== b.length || !timingSafeEqual(a, b)) {
    return { status: 401 };
  }
  const event = JSON.parse(body);
  if (seen.has(event.id)) return { status: 200, takror: true };
  seen.add(event.id);
  return { status: 200, qabul: event.id };
}

const seen = new Set();
const body = '{"id":"evt_17","summa":35000}';
console.log(handleWebhook(body, sign(body), seen));
console.log(handleWebhook(body, sign(body), seen));
console.log(handleWebhook(body, "0".repeat(64), seen));

Konsolda:

text
{ status: 200, qabul: 'evt_17' }
{ status: 200, takror: true }
{ status: 401 }

Takrorga ham 200 — aks holda xizmat "yetib bormadi" deb yana yuboraveradi. Bu idempotentlik: bir xil so'rov necha marta kelsa ham, natija bir marta. Real loyihada seen — baza jadvali (26-qism), chunki xotira server qayta ishga tushganda tozalanadi (Server Actions xavfsizligi dagi limit muammosi kabi).

4-mashq: Portfolio qadami

app/blog/rss.xml/route.ts va SAYT_URL ni qo'shing. «Tekshiruv» bo'limidagi to'rt qadamni bajaring. Qo'shimcha: post sarlavhalaridan biriga & belgisini qo'shib build qiling va lentani brauzerda oching — XML buzilmaganini tekshiring, keyin qaytaring. Commit.

Yechim

Namuna — «Kanon: RSS lenta» bo'limidagi kod. & sarlavhada &amp; bo'lib chiqadi, brauzer XML'ni xatosiz ko'rsatadi. Lentani Feedly yoki Telegram RSS botiga qo'shish uchun sayt internetda bo'lishi kerak — bu Deploy darsidan keyin. Commit xabari — kanondagi.

11. Real ishda

  • Webhook'lar — Route Handler'larning eng muhim ishi: to'lov (Click, Payme, Stripe), Telegram bot, GitHub, CMS. Imzo tekshiruvi — majburiy.
  • BFF API — mobil ilova va boshqa mijozlar uchun Next.js ichidagi kichik API. Katta, ko'p mijozli API esa alohida backend'da (25+ qismlar) — qaror jadvali keyingi darsda.
  • Fayl formatlari — RSS, sitemap.xml, iCal, CSV eksport, PDF — HTML bo'lmagan har narsa.
  • Intervyuda: "Route Handler va Server Action farqi?", "Webhook'ni qanday himoya qilasiz?", "CORS nima va u himoyami?".

Xulosa

  • route.ts — HTTP metod nomli funksiyalar; Web standart Request → Response; yo'q metod — 405.
  • NextRequest.nextUrl — query; dinamik segment — await ctx.params, tur RouteContext<…>.
  • Cache Components'da request ni o'qimaydigan GET — ○ (kanon RSS 30d 1y); o'qisa yoki POST — ƒ.
  • Kanon RSS: xml() qochirish, postlar(), application/rss+xml; DOMParser — xatosiz, 3 item.
  • Webhook — xom tanadan HMAC, timingSafeEqual, takrorga 200; CORS — brauzer qoidasi, himoya emas.
  • page.tsx va route.ts bitta papkada bo'lmaydi — build xatosi.

Keyingi dars: Route Handler, Server Action yoki alohida backend? — har vazifa uchun to'g'ri joyni tanlaymiz.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Next.js Route Handlers: route.ts bilan API, RSS va webhook — IlmHamroh