chaingrow-blog
v0.2.2
Published
Drop-in Next.js SDK for rendering blogs published from ChainGrow.
Downloads
351
Maintainers
Readme
chaingrow-blog
Drop-in Next.js SDK for rendering blogs published from ChainGrow.
- Zero database. Your site stores nothing — ChainGrow is the source of truth.
- Instant publishing. When you press Publish in ChainGrow, the SDK's webhook handler invalidates the relevant page via
revalidateTag. Next request serves fresh HTML. - Headless-first. The core SDK is unstyled semantic HTML. Supply your own components for any block type you want to customize.
Install
pnpm add chaingrow-blog
# or
npm install chaingrow-blogRequires Next.js ≥ 14 (App Router) and React ≥ 18.
Setup (3 files)
1. Environment variable
CHAINGROW_API_KEY=ck_live_xxxThat's the only required variable. Generate the key in your ChainGrow dashboard under Integrations → API Keys. It authenticates your API calls AND verifies incoming webhooks, so there's only one secret to manage. The webhook delivery URL is derived automatically from the website you set on your business profile, so you never paste a URL anywhere.
(Optional: set CHAINGROW_API_URL if you're pointing at staging or a self-hosted deploy. Defaults to https://api.chaingrow.de.)
2. Webhook receiver
Mount the SDK's handler at the convention path /api/chaingrow/webhook — ChainGrow always POSTs to ${your-website}/api/chaingrow/webhook:
// app/api/chaingrow/webhook/route.ts
export { POST } from 'chaingrow-blog/next/webhook';3. Blog page
Option A — Custom rendering (bring your own JSX):
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'; // Next.js built-in — serves a real HTTP 404 and renders app/not-found.tsx
import { getBlog } from 'chaingrow-blog/next';
export default async function BlogPage({
params,
}: {
params: { slug: string };
}) {
const blog = await getBlog(params.slug);
if (!blog) notFound();
return (
<article>
<h1>{blog.title}</h1>
{blog.blocks.map((b, i) => (
/* render however you want */
<YourCustomBlock key={i} block={b} />
))}
</article>
);
}Option B — Default renderer with per-block overrides:
import { notFound } from 'next/navigation';
import { getBlog } from 'chaingrow-blog/next';
import { BlogRenderer } from 'chaingrow-blog/react';
export default async function BlogPage({
params,
}: {
params: { slug: string };
}) {
const blog = await getBlog(params.slug);
if (!blog) notFound();
return (
<article className="prose">
<h1>{blog.title}</h1>
<BlogRenderer
blog={blog}
components={{
heading: ({ level, children }) =>
level === 2 ? (
<h2 className="text-3xl font-bold">{children}</h2>
) : (
<h3 className="text-2xl font-semibold">{children}</h3>
),
quote: ({ children }) => (
<blockquote className="border-l-4 border-blue-500 pl-4 italic">
{children}
</blockquote>
),
}}
/>
</article>
);
}API
getBlog(slug, language?, opts?)
Fetches a blog by slug (server-side, ISR-cached under tag chaingrow:blog:<slug>). Returns null if not found.
listBlogs(opts?, clientOpts?)
Lists blogs (optionally filtered by status or language).
createClient({ apiUrl, apiKey })
Framework-agnostic client. Use from edge functions, scripts, or non-Next.js environments.
<BlogRenderer blog={blog} components={...} />
Renders all blog.blocks using unstyled default components. Every block type is overridable via the components prop — supply only the ones you want to customize.
Block types: heading, paragraph, list, quote, code, image_placeholder.
createWebhookHandler({ apiKey?, onEvent? })
Returns a { POST } route handler. Verifies the X-ChainGrow-Signature HMAC (signed server-side with sha256(apiKey)) and calls revalidateTag. Pass onEvent to run custom logic after revalidation (e.g. cache warming, analytics). By default reads CHAINGROW_API_KEY from the environment.
Rate-limit handling
Calls are transparently retried once on HTTP 429 Too Many Requests, honoring the Retry-After header. No configuration needed. Other errors bubble up unchanged.
How publishing works
ChainGrow dashboard Your Next.js site
─────────────────── ─────────────────
Press Publish
│
▼
blog.status = PUBLISHED
│
▼
HMAC-signed webhook ──────────────► /api/chaingrow/webhook
│
▼
verify signature
│
▼
revalidateTag('chaingrow:blog:<slug>')
│
▼
Next user request /blog/<slug> cache miss
│ │
▼ ▼
Fresh HTML getBlog() → GET /api/v1/blogs/by-slug/<slug>
│
▼
cached for next visitorsReleasing new versions (maintainers only)
Consumers installing
chaingrow-blogcan ignore this section — it's internal notes for ChainGrow maintainers cutting a new release.
The SDK is published to npm by a GitHub Actions workflow (.github/workflows/publish-blog-sdk.yml). The workflow only runs when you push a git tag matching blog-sdk-v*. Regular commits and branch pushes do nothing — npm is never touched unless you explicitly create and push a release tag.
Triggers
on:
push:
tags:
- 'blog-sdk-v*' # only fires on matching tags
workflow_dispatch: # manual "Run workflow" button in GitHub Actions UIThat's it. Two ways to trigger a publish:
- Push a tag named
blog-sdk-v<version>(the normal flow) - Click "Run workflow" on the Actions page in GitHub (manual escape hatch)
No other event publishes. You can push commits to main all day without releasing.
Cutting a release
# 1. Bump the version in packages/blog-sdk/package.json
# "version": "0.1.0" → "version": "0.1.1"
# 2. Commit the version bump
git add packages/blog-sdk/package.json
git commit -m "chore(blog-sdk): release v0.1.1"
git push # normal push, nothing publishes yet
# 3. Create the release tag on that commit
git tag blog-sdk-v0.1.1
# 4. Push the tag (separate command — git push alone doesn't push tags)
git push origin blog-sdk-v0.1.1 # this is what fires the workflowAbout a minute later the new version lands on npmjs.com/package/chaingrow-blog.
Why tags and not branches?
Tags are immutable bookmarks pointing at a specific commit, and git treats them as a separate namespace from branches. Splitting the release trigger onto tags means:
- Normal development pushes never accidentally publish
- Every published version has a matching tag you can
git checkoutto see exactly what was shipped - Cutting a release is an explicit, deliberate action (
git tag+git push origin <tag>), not a side effect of merging to main
Versioning rules
Follow semver:
| Change | Bump |
|---|---|
| Adding a new field to Blog (backwards-compatible) | patch (0.1.0 → 0.1.1) |
| Adding a new exported function or component | minor (0.1.0 → 0.2.0) |
| Changing the Block type union | major (0.1.0 → 1.0.0) — customer override maps will break |
| Removing or renaming any exported symbol | major |
Fixing a mistake
If you tagged the wrong commit and it hasn't published yet:
git tag -d blog-sdk-v0.1.1 # delete local tag
git push origin :refs/tags/blog-sdk-v0.1.1 # delete remote tag
# then re-tag correctlyIf it already published: npm blocks unpublishing after 72 hours. The recipe is to deprecate the bad version and bump:
npm deprecate [email protected] "published in error — use 0.1.2"
# then cut a fresh release with the fixLicense
MIT
