Draft Mode — CMS bilan preview rejimi

~15 daq · Next.js · Ilg'or mavzular

Muammodan boshlaylik

Blog kontenti CMS da (Sanity, Contentful, Strapi, Payload). Sahifalar statik — tez va arzon (59-dars). Muharrir yangi maqola yozdi va so'raydi: "Nashr qilishdan oldin saytda qanday ko'rinishini ko'rsam bo'ladimi?"

  • Sahifa statik — u faqat nashr qilingan kontentni ko'rsatadi
  • Qoralamani ko'rish uchun nashr qilish kerak — lekin u hali tayyor emas
  • Butun saytni dinamik qilish — hamma uchun sekinlashadi

Draft Mode nima?

Draft Mode — maxsus cookie orqali faqat shu foydalanuvchi uchun statik sahifalarni dinamik render qilish va qoralama kontentni ko'rsatish.

Oddiy tashrif          → statik sahifa, nashr qilingan kontent (kesh)
Draft Mode cookie bilan → har so'rovda render, qoralama kontent

Boshqa barcha tashrif buyuruvchilar uchun sayt avvalgidek tez va statik qoladi.


Hayotiy o'xshatma

Gazeta tahririyati. Chop etilgan gazeta — hamma uchun bir xil (statik). Bosh muharrir esa maxsus ruxsatnoma bilan bosmaxonaga kirib, ertangi sonning sinov nusxasini ko'radi (draft). Ruxsatnomasiz odam faqat chiqqan gazetani oladi.


1. Yoqish uchun Route Handler

// app/api/draft/route.ts
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const maxfiy = searchParams.get('secret')
  const slug = searchParams.get('slug')

  if (maxfiy !== process.env.DRAFT_SECRET || !slug) {
    return new Response("Noto'g'ri token", { status: 401 })
  }

  // slug haqiqatan mavjudligini CMS dan tekshirish
  const post = await cms.getPost(slug, { draft: true })
  if (!post) return new Response('Topilmadi', { status: 404 })

  const draft = await draftMode()
  draft.enable()

  redirect(`/blog/${post.slug}`)
}

CMS da "Preview" tugmasi URL i:

https://dokon.uz/api/draft?secret=MAXFIY_TOKEN&slug=yangi-maqola

[!WARNING] redirect ga foydalanuvchi bergan slug ni to'g'ridan-to'g'ri qo'ymang — avval CMS dan topib, uning slug idan foydalaning. Aks holda ?slug=//boshqa-sayt.uz kabi so'rov bilan sizning domeningiz orqali begona saytga yo'naltirish mumkin (open redirect).


2. Sahifada qoralamani olish

// app/blog/[slug]/page.tsx
import { draftMode } from 'next/headers'
import { notFound } from 'next/navigation'

export async function generateStaticParams() {
  const postlar = await cms.getPostlar()
  return postlar.map((p) => ({ slug: p.slug }))
}

export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params
  const { isEnabled } = await draftMode()

  const post = await cms.getPost(slug, { draft: isEnabled })
  if (!post) notFound()

  return (
    <article>
      {isEnabled && (
        <div role="status" className="bg-amber-100 p-2 text-sm">
          Qoralama rejimi — <a href="/api/draft/ochir">chiqish</a>
        </div>
      )}
      <h1>{post.sarlavha}</h1>
      <div>{post.matn}</div>
    </article>
  )
}
  • isEnabled = false — oddiy statik sahifa, nashr qilingan kontent
  • isEnabled = true — Next.js shu so'rov uchun sahifani dinamik render qiladi; CMS dan qoralama olinadi

CMS so'rovi

// lib/cms.ts — umumiy g'oya
export async function getPost(slug: string, { draft = false } = {}) {
  const res = await fetch(`${CMS_URL}/posts?slug=${encodeURIComponent(slug)}&draft=${draft}`, {
    headers: { Authorization: `Bearer ${draft ? process.env.CMS_PREVIEW_TOKEN : process.env.CMS_TOKEN}` },
    next: draft ? undefined : { tags: ['postlar'] },
    cache: draft ? 'no-store' : 'force-cache',
  })
  const data = await res.json()
  return data[0] ?? null
}

Qoralama uchun alohida (kengroq huquqli) token va kesh siz so'rov.


3. O'chirish

// app/api/draft/ochir/route.ts
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'

export async function GET() {
  const draft = await draftMode()
  draft.disable()
  redirect('/')
}

Nashr qilinganda — keshni yangilash

Muharrir "Publish" ni bosganda CMS webhook yuboradi (35-dars):

// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache'

export async function POST(request: Request) {
  if (request.headers.get('x-webhook-secret') !== process.env.CMS_WEBHOOK_SECRET) {
    return new Response("Ruxsat yo'q", { status: 401 })
  }
  revalidateTag('postlar')
  return Response.json({ ok: true })
}

To'liq oqim:

Muharrir yozadi → "Preview" → /api/draft → cookie → qoralama ko'rinadi
Muharrir "Publish" → CMS webhook → revalidateTag → hamma yangi versiyani ko'radi

[!INFO] Ko'p CMS lar (Sanity, Contentful, Storyblok, Payload) Next.js uchun tayyor integratsiya beradi: "Visual editing" — muharrir sayt ustida to'g'ridan-to'g'ri bosib tahrirlaydi, o'zgarishlar real vaqtda ko'rinadi. Ularning ichida ham aynan shu Draft Mode ishlaydi.

[!TIP] Draft Mode cookie si sessiya cookie si — brauzer yopilganda o'chadi. Muharrirga "Chiqish" havolasini ko'rinarli joyda ko'rsating: aks holda u qoralama ko'rinishini haqiqiy sayt deb o'ylashi mumkin.


Tez-tez uchraydigan xatolar

1. Maxfiy token siz

/api/draft ochiq — har kim qoralamalarni (masalan, e'lon qilinmagan aksiya narxlarini) ko'ra oladi.

2. Qoralama so'rovi keshlanadi

force-cache qoralama uchun ham — muharrir eski qoralamani ko'radi. Draft da no-store.

3. Open redirect

redirect(searchParams.get('slug')) — tekshiruvsiz.


Xulosa

Draft Mode — cookie orqali faqat shu foydalanuvchi uchun dinamik render va qoralama
/api/draft: secret tekshiruvi → CMS dan topish → draftMode().enable() → redirect
Sahifada: const { isEnabled } = await draftMode() → cms.getPost(slug, { draft })
Qoralama — preview token, no-store; oddiy — force-cache + tags
/api/draft/ochir — disable(); "Qoralama rejimi" belgisini ko'rsating
Publish → webhook → revalidateTag

Keyingi dars

74-dars: OpenTelemetry — monitoring va tracing

So'rov ilova ichida qayerda vaqt sarflaganini ko'rish.