Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.neuraldraft.io/llms.txt

Use this file to discover all available pages before exploring further.

This snippet wires Neural Draft into Astro 5 via a content collection loader. The result: type-safe blog content fetched at build time, plus a webhook handler that triggers Vercel/Cloudflare/Netlify rebuilds when something changes.

Install

npm install @neuraldraft/sdk
NEURAL_DRAFT_API_KEY=ndsk_live_...
NEURAL_DRAFT_WEBHOOK_SECRET=whsec_...

Shared client

import { NeuralDraft } from "@neuraldraft/sdk";
import { createHmac, timingSafeEqual } from "node:crypto";

const apiKey = import.meta.env.NEURAL_DRAFT_API_KEY ?? process.env.NEURAL_DRAFT_API_KEY;
if (!apiKey) throw new Error("Missing NEURAL_DRAFT_API_KEY");

export const nd = new NeuralDraft({ apiKey });

export function verifyNeuralDraftSignature(
  body: string,
  header: string,
  secret = import.meta.env.NEURAL_DRAFT_WEBHOOK_SECRET ?? "",
  toleranceSeconds = 300
): boolean {
  const parts = Object.fromEntries(
    header.split(",").map((p) => p.split("=") as [string, string])
  );
  const t = Number(parts.t);
  const v1 = parts.v1;
  if (!Number.isFinite(t) || !v1 || !secret) return false;

  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - t) > toleranceSeconds) return false;

  const signed = `${t}.${body}`;
  const expected = createHmac("sha256", secret).update(signed).digest("hex");
  const a = Buffer.from(expected, "hex");
  const b = Buffer.from(v1, "hex");
  return a.length === b.length && timingSafeEqual(a, b);
}

A content collection loader

Astro’s content layer treats Neural Draft like a CMS — pull posts at build time, get type-safe content with Zod validation.
import { defineCollection, z } from "astro:content";
import { nd } from "./lib/neural-draft";

const blog = defineCollection({
  loader: async () => {
    const all: any[] = [];
    let page = 1;
    while (true) {
      const res = await nd.blogPosts.list({
        status: "published",
        lang: "en",
        per_page: 100,
        page,
      });
      all.push(...res.data);
      if (res.data.length < 100) break;
      page++;
    }
    return all.map((p) => ({
      id: p.slug,
      slug: p.slug,
      title: p.title,
      excerpt: p.excerpt,
      content: p.content,
      featured_image: p.featured_image,
      published_at: p.published_at,
      category: p.category?.name ?? null,
      tags: (p.tags ?? []).map((t: any) => t.name),
    }));
  },
  schema: z.object({
    slug: z.string(),
    title: z.string(),
    excerpt: z.string().nullable(),
    content: z.string(),
    featured_image: z.string().url().nullable(),
    published_at: z.string().nullable(),
    category: z.string().nullable(),
    tags: z.array(z.string()),
  }),
});

export const collections = { blog };

A blog index page

---
import { getCollection } from "astro:content";

const posts = (await getCollection("blog")).sort((a, b) =>
  (b.data.published_at ?? "").localeCompare(a.data.published_at ?? "")
);
---

<main class="prose mx-auto py-12">
  <h1>Blog</h1>
  <ul>
    {posts.map((post) => (
      <li>
        <a href={`/blog/${post.data.slug}`}>
          <h2>{post.data.title}</h2>
          <p>{post.data.excerpt}</p>
        </a>
      </li>
    ))}
  </ul>
</main>
---
import { getCollection } from "astro:content";

export async function getStaticPaths() {
  const posts = await getCollection("blog");
  return posts.map((post) => ({ params: { slug: post.data.slug }, props: { post } }));
}

const { post } = Astro.props;
---

<article class="prose mx-auto py-12">
  <h1>{post.data.title}</h1>
  {post.data.featured_image && (
    <img src={post.data.featured_image} alt={post.data.title} />
  )}
  <div set:html={post.data.content} />
</article>

A webhook handler that triggers a rebuild

Astro itself doesn’t trigger rebuilds, but every modern host (Vercel, Netlify, Cloudflare) gives you a “deploy hook” URL. Forward Neural Draft webhooks to that URL.
import type { APIRoute } from "astro";
import { verifyNeuralDraftSignature } from "../../lib/neural-draft";

const REBUILD_HOOK = process.env.DEPLOY_HOOK_URL!;

export const POST: APIRoute = async ({ request }) => {
  const body = await request.text();
  const sig = request.headers.get("x-neural-draft-signature") ?? "";
  if (!verifyNeuralDraftSignature(body, sig)) {
    return new Response("invalid signature", { status: 401 });
  }

  const event = JSON.parse(body) as { type: string };
  if (
    event.type === "blog_post.published" ||
    event.type === "blog_post.translated" ||
    event.type === "content.changed"
  ) {
    await fetch(REBUILD_HOOK, { method: "POST" });
  }

  return new Response(JSON.stringify({ received: true }), {
    headers: { "content-type": "application/json" },
  });
};

Notes

  • This file uses Astro’s content.config.ts — Astro 5 syntax. On 4.x, use the defineCollection({ loader }) style from the experimental content layer.
  • For multi-language sites, add import.meta.env.PUBLIC_LANG and clone the collection per locale.
  • The deploy hook is per-platform: Vercel, Netlify and Cloudflare Pages all expose one in project settings. Free, no rate limit.