A blog with authors, tags, pagination and an RSS feed, built with Astro and Draftbase. Posts are fetched at build time and deployed to GitHub Pages as static HTML — no server, no runtime API calls.
Step up from example-portfolio: this one adds reference fields, entry tags, pagination and feed generation.
npm install
cp .env.example .env # add your API keys
npm run seed # creates the templates + 5 sample posts in your org
npm run devSeeded by scripts/seed.mjs. A template's key (author, blogPost) is what the delivery
API calls templateId.
author
| Field | Type | |
|---|---|---|
name |
text | required |
slug |
text | required, used as the URL |
bio |
richText | |
avatar |
media |
blogPost
| Field | Type | |
|---|---|---|
title |
text | required |
slug |
text | required, used as the URL |
excerpt |
text | max 240 chars, used for meta description and cards |
body |
richText | required |
cover |
media | also used as the Open Graph image |
publishedDate |
date | sorts the archive, newest first |
author |
reference → author |
Tags are not a field. They live on the entry itself (entry.tags), so any entry of any
template can carry them and src/pages/tags/[tag].astro builds a page per distinct tag.
src/lib/draftbase.tswraps the SDK.getAll(templateId, include)follows cursor pagination to the end of the collection.include: 1resolvesreferenceandmediafields into nested objects, sopost.fields.author.fields.nameworks without a second request. Depth can go to 5 — example-course uses depth 2.src/pages/[...page].astrouses Astro'spaginate()to emit/,/2,/3from one file.- Rich text is rendered with
toHtml()from@draftbase/renderer— a plain HTML string, no React. JSX inside the source (e.g.<Callout>) passes through as literal tags; to render real components, usecompileMDX()inside a framework island instead. src/pages/rss.xml.tsreuses the sametoHtml()output for full-content feed items.
reactis independencieseven though this site ships no React.@draftbase/rendererexportstoHtmlandMDXContentfrom one entry point, so importing either one pulls the React import in at bundle time. No React reaches the browser — Astro renders this at build time and the output is plain HTML.
The delivery API key is build-time only:
- It is read through
import.meta.env.DRAFTBASE_API_KEYin server code. Astro only exposesPUBLIC_-prefixed variables to the browser, so this one cannot end up in the bundle. Do not rename it toPUBLIC_DRAFTBASE_API_KEY. - In CI it comes from the
DRAFTBASE_API_KEYrepository secret. - Use a delivery-scoped key. It is read-only and only ever returns published entries — drafts are invisible to it, so an unpublished post cannot leak into a build.
- The management key (
DRAFTBASE_MANAGEMENT_API_KEY) is only fornpm run seed, and belongs in your local.envand nowhere else. It can write and delete content. .envis gitignored. Only.env.example, which holds no values, is committed.
Verify for yourself after a build: grep -r "$(grep DRAFTBASE_API_KEY .env | cut -d= -f2)" dist/
should find nothing.
- Settings → Pages → Source: GitHub Actions.
- Settings → Secrets and variables → Actions → add
DRAFTBASE_API_KEY(delivery-scoped). - Optional: on the same page, add a repository variable
DRAFTBASE_ENVIRONMENTif your content lives in an environment other thanproduction. - Push to
main.
This repo deploys to the custom domain in public/CNAME, so it sets no base.
Forking to a project site (<user>.github.io/<repo>)? Delete public/CNAME and add
base: "/<repo>" to astro.config.mjs.
Add a Draftbase webhook pointing at:
POST https://api.github.com/repos/<owner>/example-blog/dispatches
{ "event_type": "draftbase-publish" }
with an Authorization: Bearer <fine-grained PAT> header, scoped to this repo with
Contents: read and write. That token lives in Draftbase's webhook config — never in this
repo.
- Comments — needs a server or a third-party embed.
- Search — see example-course for a build-time search index that works without one.
- Draft previews — need a running server; static builds only ever see published entries.