Build a blog end-to-end
This guide takes an empty app to a live blog that reads your published articles, ranks, and updates itself when you publish. It uses Next.js as the worked example and points to the Astro and React Router equivalents at each step. It orchestrates the per-SDK references rather than repeating them — follow the links for the full surface.
The whole thing reads from your keyless static feed: your siteId is the only key, there is no API key, and reads come straight from feeds.ghostwritr.io/{siteId}/.
-
Install the SDK and the renderer.
npm install @ghostwritr/next @ghostwritr/reactpnpm add @ghostwritr/next @ghostwritr/reactyarn add @ghostwritr/next @ghostwritr/reactbun add @ghostwritr/next @ghostwritr/react@ghostwritr/nextis the server-only data client;@ghostwritr/reactgives you<ArticleContent>to render the Markdown body. -
Get your site ID. It’s the unguessable read capability for your feed — there’s nothing else to provision. See Your site ID. Put it in
.env.local:.env.local GHOSTWRITR_SITE_ID=your-site-idIn Astro this is
GHOSTWRITR_SITE_IDread viaimport.meta.env; in React Router it’sprocess.env.GHOSTWRITR_SITE_IDinside your server-side loaders. -
Create the data client as a module singleton in a
server-onlyfile.createGhostwritrlives at the/serversubpath:lib/ghostwritr.ts import "server-only";import { createGhostwritr } from "@ghostwritr/next/server";export const gw = createGhostwritr({siteId: process.env.GHOSTWRITR_SITE_ID!, // public read key — no API keyrevalidate: 3600, // ISR window in seconds (default 3600)});The Astro equivalent is a Content Layer loader in
src/content.config.ts(ghostwritr({ siteId })); React Router has no client object — you callfetchArticles/fetchArticledirectly inside a routeloader. -
Build the index, newest first.
getArticles()returns every published article, already sorted:app/blog/page.tsx import Link from "next/link";import { gw } from "@/lib/ghostwritr";export default async function Blog() {const articles = await gw.getArticles();return (<ul>{articles.map((a) => (<li key={a.id}><Link href={`/blog/${a.slug}`}>{a.title}</Link></li>))}</ul>);}Each item is a full Article —
title,slug,image,publishedAt,author, and the rest are right there if you want a richer card. -
Build the article page.
gw.generateStaticParamsis ready to export as-is, so every slug is prerendered. Render the Markdown with<ArticleContent>:app/blog/[slug]/page.tsx import { notFound } from "next/navigation";import { ArticleContent } from "@ghostwritr/react";import { gw } from "@/lib/ghostwritr";export const generateStaticParams = gw.generateStaticParams;export default async function Page({params,}: {params: Promise<{ slug: string }>;}) {const { slug } = await params;const article = await gw.getArticle(slug);if (!article) notFound();return (<article><h1>{article.title}</h1><ArticleContent markdown={article.markdown} className="prose" /></article>);} -
Drive SEO from the article fields. Export
generateMetadataon the article route and feed<title>, the canonical URL, Open Graph, and JSON-LD fromseoTitle,seoDescription,canonicalUrl,image, andauthor. The full Next recipe (plusapp/sitemap.ts) is in SEO metadata & sitemap; the cross-framework reasoning is in SEO & structured data. React Router users get this for free fromarticleMeta. -
Make publishing instant. ISR refreshes on the
revalidateclock; to make a just-published article appear in seconds, wire the signedfeed.updatedwebhook withcreateRevalidateHandlerfrom@ghostwritr/next/revalidate. See Instant publishing and the concept page Instant updates. -
Deploy. Run
next dev, open/blog, then ship to your host of choice. The feed is static and cached at the edge, so reads stay cheap at any traffic. If your blog lives on its own domain, decide the canonical policy up front — see Custom domain & canonical.
That’s a complete, statically-generated, self-updating blog reading from the keyless feed.
What to reach for next
Section titled “What to reach for next”- Other frameworks — the same blog in Astro or React Router.
- Customize the render — code highlighting,
next/image, custom links. See Customizing rendering. - Error handling — what to show when the feed is unreachable or a slug is gone. See Error handling.