Skip to content

The content feed

Every read in every SDK resolves to one place: the static content feed at feeds.ghostwritr.io/{siteId}/. It is a cacheable projection of your published articles, served from R2 behind Cloudflare’s CDN — free egress, fast at the edge, and keyless.

The feed is not a database you query. It is a snapshot of exactly the articles that are currently published for your site, rebuilt on publish: when an article goes live, is updated, or is unpublished, Ghost Writr regenerates the static files. Between rebuilds the feed is fixed, which is what makes it safe to cache aggressively.

Under your site prefix, the feed is a small manifest plus immutable per-build snapshots.

feeds.ghostwritr.io/{siteId}/
feed/index.json — the manifest (the only mutable file)
b/{buildId}/page-1.json — 20 articles, newest first
b/{buildId}/page-2.json
b/{buildId}/by-slug/{slug}.json — one article, by slug
  1. The manifest is the entry point. feed/index.json carries a buildId, a pageCount, and a total. It is the only file that changes in place — it always points at the latest build.

  2. The build is immutable. Each rebuild writes a fresh b/{buildId}/... tree and then flips the manifest’s buildId to it. Because a build’s keys never change, anything under b/{buildId}/ can be cached forever.

  3. Pages hold the list. Each b/{buildId}/page-N.json holds up to 20 articles, newest first. The manifest’s pageCount tells a client how many to expect.

  4. By-slug holds one. b/{buildId}/by-slug/{slug}.json resolves a single article for a dynamic route without paging through the whole list — it returns the article, or a 404 the SDK surfaces as null.

The fetchers always start from the manifest, then read from the build it names — never a hard-coded page or slug URL.

  • fetchArticles({ siteId }) resolves the manifest, then fetches page-1 … page-{pageCount} in order and concatenates them, newest first. (It de-duplicates by id defensively, though immutable per-build keys mean an id should appear once.)
  • fetchArticle({ siteId, slug }) resolves the manifest, then reads exactly one by-slug/{slug}.json — cheaper than walking every page for a [slug] route. A missing object is null, not an error.
import { fetchArticles, fetchArticle } from "@ghostwritr/feed";
const all = await fetchArticles({ siteId }); // manifest → every page
const one = await fetchArticle({ siteId, slug }); // manifest → by-slug, or null

Because the snapshot only changes on publish, a consumer can cache it and refresh on its own schedule — or react instantly. Ghost Writr can POST a signed feed.updated webhook the moment a rebuild lands, so you revalidate within seconds instead of waiting on a timer. See Instant updates.