npm.io
0.3.0 • Published 2d ago

@usezanin/blog

Licence
MIT
Version
0.3.0
Deps
4
Size
102 kB
Vulns
0
Weekly
0

@usezanin/blog

React SDK for rendering a Zanin blog inside any Next.js (App Router) site. Two page files and you have a fully styled, SEO-complete blog whose content agents manage through the Zanin API/MCP.

npm install @usezanin/blog

Setup

Env vars: ZANIN_BASE_URL, ZANIN_SITE_ID, ZANIN_SITE_KEY (from the Zanin dashboard), optionally ZANIN_SITE_URL (your site's public origin, for canonical URLs/RSS) and ZANIN_REVALIDATE_SECRET. Rendering only reads, so make ZANIN_SITE_KEY a read-only key scoped to this site (Settings → API keys) rather than a full-write workspace credential.

// app/layout.tsx — import the themable styles once
import "@usezanin/blog/styles.css";
// app/blog/page.tsx
import { BlogIndex } from "@usezanin/blog";

export default function BlogPage() {
  return <BlogIndex title="Blog" description="Notes from the team." />;
}
// app/blog/[slug]/page.tsx
import { BlogPost, generateBlogMetadata, generateBlogStaticParams } from "@usezanin/blog";

export async function generateStaticParams() {
  return generateBlogStaticParams();
}

export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
  return generateBlogMetadata((await params).slug);
}

export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
  return <BlogPost slug={(await params).slug} />;
}

Optional one-line route handlers:

// app/blog/rss.xml/route.ts
import { createRssHandler } from "@usezanin/blog";
export const GET = createRssHandler();

// app/blog/sitemap.xml/route.ts
import { createSitemapHandler } from "@usezanin/blog";
export const GET = createSitemapHandler();

// app/api/zanin/revalidate/route.ts — point the site's webhook here for instant publishes
import { createRevalidateHandler } from "@usezanin/blog";
export const POST = createRevalidateHandler();

Static export (output: "export")

Statically exported sites have no server at runtime, so:

// app/blog/rss.xml/route.ts — add force-static so the file is emitted at build time
import { createRssHandler } from "@usezanin/blog";
export const dynamic = "force-static";
export const GET = createRssHandler();
// same for app/blog/sitemap.xml/route.ts

Skip createRevalidateHandler entirely — there is no ISR cache to bust. Instead point the site's revalidate webhook URL (Zanin dashboard → Settings → Sites) at a Vercel Deploy Hook (or your CI's equivalent): publishing a post then triggers a rebuild that pulls fresh content through generateStaticParams. The build environment needs ZANIN_SITE_KEY — use a read-only site-scoped key.

Theming — fully customizable

Blogs are themable end to end: colors, fonts, sizes, spacing, radius, content width, and index layout.

  1. Dashboard / agents (recommended) — set the theme per site in the Zanin dashboard ("Customize theme") or let agents call the update_site_theme MCP tool. The SDK fetches it with the content and re-renders instantly via the revalidation webhook. No deploy.
  2. Host code overrides — pass theme in the config (wins over the dashboard theme):
const config = {
  theme: {
    accent: "#0a7d54",
    fontHeading: "'Playfair Display', Georgia, serif", // load the font in your layout
    fontBody: "Inter, sans-serif",
    measure: "680px",
    radius: "16px",
    spacing: "relaxed",     // compact | comfortable | relaxed
    indexLayout: "grid",    // list | grid
  },
} satisfies ZaninBlogConfig;
  1. Plain CSS — every value is a --zanin-* CSS variable on .zanin-blog; override any of them (or restyle .zanin-* component classes) in your own stylesheet.
  2. Fully headless — build your own layouts with the exported data helpers and pieces: getBlogPosts(), getBlogPost(slug), getBlogSite() (includes the theme), PostCard, renderMdx, builtinComponents, themeToStyle.

Custom fonts: load them however you like (next/font, <link> tags) and reference the family name in fontHeading / fontBody / fontMono.

What you get

  • Server-rendered MDX with built-in components: Callout, Figure, Chart (bar/line/area/pie from inline data), CTA, YouTube, Tweet, ComparisonTable, FAQ (takes items={[{ q, a }]} and emits FAQPage JSON-LD), styled code blocks — exact prop tables live in builtinComponentDocs (re-exported here), the get_builtin_components MCP tool, and the deployed /docs page
  • validateMdx(source, { knownComponents }) — the same compiler BlogPost uses, as a linter: MDX errors, unknown component tags, unknown/missing props on built-ins
  • Per-site custom components: agents upload TSX to the CMS; the SDK loads the compiled result at render time — no redeploy
  • SEO: canonical/OG/Twitter metadata, JSON-LD Article schema, static params for SSG, RSS + sitemap
  • ISR with instant, webhook-driven revalidation on publish
  • Theming via --zanin-* CSS variables (accent, fonts, measure, radii)

MIT

Keywords