Next.js integrácia

Auto-publish článkov na Next.js web

Návod krok po kroku, ako pripojiť svoj Next.js projekt (App Router aj Pages Router) k Optimalizácii pre AI. 1 súbor, 1 env premenná, 1 insert do DB — typicky 15-30 minút práce.

1. Predpoklady

  • Plán PRO alebo AGENCY v Optimalizácii pre AI.
  • Existujúci Next.js projekt na vlastnej doméne (verzia 13+, App alebo Pages Router).
  • Možnosť deploy-núť zmenu (1 nový súbor + 1 env premenná).
  • Miesto, kam články uložiť — typicky Postgres+Prisma, Supabase, alebo MDX súbory v repe.

2. Získať credentials v dashboarde

  1. Otvorte /dashboard/blog → banner „Nastavte publikovanie článkov"Nastaviť.
  2. V kroku „Vyber platformu" klikni Webhook (auto-detect to často trafí — Next.js posiela hlavičku x-powered-by: Next.js).
  3. Vyplň formulár:
    • Site URL: https://mojfirma.sk/api/aio-webhook — presne táto cesta (vytvoríš ju v ďalšom kroku).
    • API Key (Bearer) — voliteľné, slúži ako prvá vrstva auth.
    • Webhook Secret — klik „Vygenerovať" → skopíruj 64-znakový hex string. Tento si odlož — bude potrebný v kroku 4.
  4. Klikni „Zobraziť kód pre môj framework" → tab Next.js (App Router)Kopírovať.

Zatiaľ nestláčaj „Test connection" — endpoint na tvojom webe ešte neexistuje. Test spustíš až po kroku 5.

3. Vytvoriť webhook endpoint v Next.js

App RouterVytvor súbor app/api/aio-webhook/route.ts

app/api/aio-webhook/route.ts
TypeScript
import { NextRequest, NextResponse } from 'next/server'
import { createHmac, timingSafeEqual } from 'crypto'

export const runtime = 'nodejs'

const SECRET = process.env.AIO_WEBHOOK_SECRET!

export async function POST(req: NextRequest) {
  const raw = await req.text()
  const { event, data } = JSON.parse(raw)

  // Test connection — wizard posiela {event:'test'} bez podpisu, povolíme prejsť
  if (event === 'test') {
    return NextResponse.json({ externalId: 'test', externalUrl: 'https://example.com' })
  }

  // Pre article.published vyžadujeme platný HMAC podpis
  const sig = req.headers.get('x-webhook-signature') || ''
  const expected = 'sha256=' + createHmac('sha256', SECRET).update(raw).digest('hex')
  if (
    sig.length !== expected.length ||
    !timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
  ) {
    return NextResponse.json({ error: 'bad signature' }, { status: 401 })
  }

  // ⬇️ Tu uložte článok do svojej DB (Prisma / Supabase / iné)
  // dostupné: data.title, data.htmlContent, data.excerpt, data.slug,
  //           data.coverImageUrl, data.seoTitle, data.metaDesc,
  //           data.tags, data.schemaMarkup

  return NextResponse.json({
    externalId: data.slug,
    externalUrl: `https://mojfirma.sk/blog/${data.slug}`,
  })
}

Pages RouterAlternatívne: pages/api/aio-webhook.ts

pages/api/aio-webhook.ts
TypeScript
import type { NextApiRequest, NextApiResponse } from 'next'
import { createHmac, timingSafeEqual } from 'crypto'

export const config = { api: { bodyParser: false } }

const SECRET = process.env.AIO_WEBHOOK_SECRET!

async function readRaw(req: NextApiRequest): Promise<string> {
  const chunks: Buffer[] = []
  for await (const chunk of req) chunks.push(Buffer.from(chunk))
  return Buffer.concat(chunks).toString('utf8')
}

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== 'POST') return res.status(405).end()

  const raw = await readRaw(req)
  const { event, data } = JSON.parse(raw)

  // Test connection — bez podpisu
  if (event === 'test') return res.status(200).json({ externalId: 'test' })

  // Pre article.published vyžadujeme platný HMAC podpis
  const sig = (req.headers['x-webhook-signature'] as string) || ''
  const expected = 'sha256=' + createHmac('sha256', SECRET).update(raw).digest('hex')

  if (
    sig.length !== expected.length ||
    !timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
  ) return res.status(401).json({ error: 'bad signature' })

  // ⬇️ Tu uložte článok

  res.status(200).json({
    externalId: data.slug,
    externalUrl: `https://mojfirma.sk/blog/${data.slug}`,
  })
}

HMAC verifikácia: Každý webhook od nás je podpísaný hlavičkou x-webhook-signature: sha256=... cez SHA-256 a váš secret. timingSafeEqual chráni pred timing attackmi.

4. Pridať env premennú

V lokálnom .env.local:

.env.local
bash
AIO_WEBHOOK_SECRET=<sem vlož secret z kroku 2>

Na Verceli:

  • Project Settings → Environment Variables → Add
  • Name: AIO_WEBHOOK_SECRET
  • Value: ten istý secret
  • Scope: Production + Preview (oba)

Po pridaní env premennej Vercel nereštartuje existujúce nasadenie automaticky — buď redeploy alebo nový commit.

5. Uložiť článok do DB

V mieste označenom // ⬇️ Tu uložte článok doplňte insert podľa toho, čo používate. Tri najbežnejšie varianty:

Prisma + Postgres

await prisma.post.create({
  data: {
    title: data.title,
    slug: data.slug,
    htmlContent: data.htmlContent,
    excerpt: data.excerpt,
    coverImageUrl: data.coverImageUrl,
    seoTitle: data.seoTitle,
    metaDesc: data.metaDesc,
    tags: data.tags,
    publishedAt: new Date(),
  },
})

// Optional: revalidate ISR cache
revalidatePath('/blog')
revalidatePath(`/blog/${data.slug}`)

Supabase

const { error } = await supabase.from('posts').insert({
  title: data.title,
  slug: data.slug,
  html_content: data.htmlContent,
  excerpt: data.excerpt,
  cover_image_url: data.coverImageUrl,
  seo_title: data.seoTitle,
  meta_desc: data.metaDesc,
  tags: data.tags,
  published_at: new Date(),
})

if (error) {
  return NextResponse.json({ error: error.message }, { status: 500 })
}

MDX súbory (statický blog)

import { writeFile } from 'fs/promises'
import path from 'path'

const frontmatter = `---
title: "${data.title}"
slug: "${data.slug}"
date: ${new Date().toISOString()}
cover: ${data.coverImageUrl}
excerpt: "${data.excerpt}"
---

`

await writeFile(
  path.join(process.cwd(), 'content/blog', `${data.slug}.mdx`),
  frontmatter + data.htmlContent
)

// + trigger Vercel deploy hook to rebuild

Presnú schému payload-u (všetky polia, ktoré dostanete) nájdete v Webhook payload reference.

6. Deploy a test

git add app/api/aio-webhook/route.ts
git commit -m "Add AIO Tracking webhook endpoint"
git push

Po deploye sa vráťte do nášho dashboardu:

  1. Otvorte wizard znova (/dashboard/blog).
  2. Klik „Test connection" — my pošleme POST so { event: 'test' }.
  3. Ak váš endpoint odpovie 200 OK → ✅ zelená fajka.
  4. Zapnite toggle „Auto-publish" → uložte.

7. Ako beží auto-flow

Od momentu, ako zapnete Auto-publish:

1

Content Hub vygeneruje a finalizuje článok

2

Platforma skontroluje, či máte Auto-publish ON a aktívnu Webhook connection

3

pg-boss queue dostane publish job

4

Worker zavolá publish() → POST na váš endpoint s HMAC podpisom

5

Váš endpoint overí podpis → uloží do DB → vráti { externalId, externalUrl }

6

V dashboarde uvidíte pri článku „Publikované na mojfirma.sk → otvoriť"

8. Checklist a riešenie problémov

Mini-checklist (pošlite vývojárovi)

  • ☐ PRO/AGENCY plán aktívny
  • ☐ V dashboarde: Webhook secret vygenerovaný a skopírovaný
  • ☐ Súbor app/api/aio-webhook/route.ts vytvorený
  • AIO_WEBHOOK_SECRET v .env.local + na Verceli
  • ☐ Insert do DB doplnený (Prisma / Supabase / iné)
  • ☐ Deploy hotový (Vercel ukáže „Ready")
  • ☐ Test connection v dashboarde = ✅
  • ☐ Auto-publish toggle ON

Najčastejšie problémy

ProblémPríčinaFix
401 bad signatureIný secret v FE vs. v envSkopírujte secret znova, redeploy
404Endpoint na inej cesteSite URL musí byť presne /api/aio-webhook
Test ✅, článok sa neuložíTODO blok nedoplnenýPridajte insert (viď krok 5)
DB má článok, web nieNext.js ISR cacheVolajte revalidatePath()
SECRET is undefinedEnv premenná nie je v Production scopeVercel → Env Vars → preklikni scope

Iný framework?

Vo wizarde priamo v dashboarde máte pripravené copy-paste snippety aj pre:

AstroRemixSvelteKitNode.js / ExpressPHP

Princíp je rovnaký — POST endpoint, HMAC verifikácia, insert do DB.

Hotový začať?

Otvorte wizard a vygenerujte si secret.