crontent
Integration·HTML & CSS·beginner

Automated blog posts for HTML sites

Automated blog posts for HTML sites: write body_html files from a verified Crontent webhook into a static HTML and CSS blog.

How-to: Integrate Crontent with HTML & CSS. Official docs: HTML & CSS. Shared API contract: Publishing API and SDK.

Verify the Crontent webhook, write a static .html file from body_html (or a template partial), and deploy only after review.

Field mapping

Crontent fieldHTML output
title<h1> in page body
seo_title<title>, <meta property="og:title"> (fallback: title)
slugFilename: {slug}.html
excerpt<meta name="description">
body_htmlMain article content (preferred)
body_mdxConvert to HTML first if body_html is null
tags<meta name="keywords"> or tag list markup
hero_image_url<meta property="og:image">, hero <img>
canonical_url<link rel="canonical">
sources[]<footer> citations list

Webhook handler

ts
import { Crontent } from "@crontent/sdk";
import { writeFile, mkdir } from "node:fs/promises";
import { join } from "node:path";

const crontent = new Crontent({ apiKey: process.env.CRONTENT_API_KEY! });
const TEMPLATE = `<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>{{seoTitle}}</title>
  <meta name="description" content="{{excerpt}}">
  <link rel="canonical" href="{{canonical}}">
</head>
<body>
  <article>
    <h1>{{title}}</h1>
    {{body}}
  </article>
</body>
</html>`;

export async function handle(req: Request) {
  const rawBody = await req.text();
  const valid = await crontent.webhooks.verify({
    rawBody,
    signature: req.headers.get("x-crontent-signature"),
    timestamp: req.headers.get("x-crontent-timestamp"),
    secret: process.env.CRONTENT_WEBHOOK_SECRET!,
  });
  if (!valid) return new Response("unauthorized", { status: 401 });

  const { post_id } = JSON.parse(rawBody);
  const post = await crontent.posts.get(post_id);
  const html = TEMPLATE
    .replace("{{seoTitle}}", post.seo_title ?? post.title ?? "")
    .replace("{{title}}", post.title ?? "")
    .replace("{{excerpt}}", post.excerpt ?? "")
    .replace("{{canonical}}", post.canonical_url ?? "")
    .replace("{{body}}", post.body_html ?? "");

  const draftDir = join(process.cwd(), "drafts");
  await mkdir(draftDir, { recursive: true });
  await writeFile(join(draftDir, `${post.slug}.html`), html);
  return new Response("ok");
}

Write to a drafts/ folder first. Copy to public/ or your web root only after manual review.

Static hosting

Works with any host that serves files: S3, Cloudflare R2, nginx, GitHub Pages. Pair with CI to upload approved drafts.

Backfill

Loop posts.list and generate HTML for each missing slug. Store in drafts/ and review in batch.

Further reading