How to Use Scaffold
A guide to installing, running, and managing the Scaffold template.
Scaffold is a website starter built with Astro. It gives you a ready-to-use site with a page builder, a content management system, and a set of reusable components.
This guide walks you through installation, day-to-day editing, and the main concepts you need to know.
Getting started
Prerequisites
You'll need Git, Node.js (v20 or later), and a GitHub account. A basic familiarity with the command line is helpful but not required beyond the steps listed here.
Installation
Clone the repository and install dependencies:
npx create-astro --template draftlab-org/scaffoldcd your-project-namenpm installRunning the development server
npm run devThis starts a local server, usually at http://localhost:4321. Changes you make to files will appear in the browser almost instantly.
Building for production
npm run buildThis generates a fully static site in the dist/ folder. You can preview the production build locally with:
npm run previewDeploying
Scaffold is pre-configured for Netlify. Connect your GitHub repository to a Netlify site and every push to main will trigger a new build. No additional configuration is needed — the netlify.toml and Astro adapter are already set up.
You can also deploy to any platform that supports static sites (Vercel, Cloudflare Pages, GitHub Pages, etc.) by swapping the Astro adapter in astro.config.mjs.
How the site works
Pages and sections
Every page on the site is defined by a YAML file in src/content/pages/. A page file looks something like this:
title: About Usstatus: publishedpermalink: aboutsections: - type: hero title: About Our Organization subtitle: Building technology for the public good. - type: richText content: | We are a small team of designers and engineers... - type: people category: LeadershipThe sections array is the core of the page builder. Each section has a type that determines which component renders it. Sections are rendered in order, top to bottom.
Available section types
| Type | What it does |
|---|---|
| hero | Full-width banner with title, optional subtitle, and optional background image |
| richText | Markdown content — supports headings, lists, links, images, code blocks, tables. Optional table of contents sidebar |
| button | A row of call-to-action buttons with configurable variants and sizes |
| card | A grid of content cards, each with optional image, markdown text, and button |
| people | A grid of team member profiles pulled from the People collection. Filterable by category |
| partners | A grid of partner logos pulled from the Partners collection. Filterable by category |
| articlesRoll | A feed of recent articles, with optional category filter and limit |
| featuredPartners | A curated row of partner logos, selected by ID or shown by featured flag |
| resourcesRoll | A feed of recent resources (reports, guides, case studies) |
| flexi | A container for nesting other sections (richText, button, card) inside a single block |
Every section can also set a background with bgColor (white, gray, or gradient) to create visual rhythm on the page.
Content collections
Beyond pages, the site manages several types of content:
-
Articles (
src/content/articles/) — Markdown blog posts with frontmatter for title, excerpt, authors, tags, and hero image. -
Docs (
src/content/docs/) — Multi-chapter markdown documentation. Each doc belongs to a chapter and has an order, so you can build a structured handbook, user guide, or reference section. -
People (
src/content/people/) — Team member profiles with name, title, headshot, affiliation, and category tags. -
Partners (
src/content/partners/) — Organizations with logo, URL, category, and display order. -
Resources (
src/content/resources/) — Publications like reports, whitepapers, and guides with external download links. -
Navigation (
src/content/navigation/) — Menu definitions for the header and footer. Supports single links and dropdown menus. -
Site Settings (
src/content/site/config.json) — Global configuration: site title, description, social links, favicon, Open Graph image, cookie consent, and footer text.
Each collection has a schema defined in src/content/config.ts. Astro validates every file against its schema at build time, so you'll get a clear error message if something is missing or malformed.
Content status
All content supports a status field with three values:
-
draft — Only visible when running the development server.
-
published — Visible everywhere.
-
archived — Still visible, but can be styled differently or filtered out.
This lets you work on new content without publishing it, and retire old content without deleting it.
Embedding videos and social posts
Any rich-text or markdown content — article bodies, docs, and richText page sections — supports embeds for YouTube, Vimeo, Bluesky, and Mastodon using a plain markdown link with the literal text EmbedLink:
[EmbedLink](https://www.youtube.com/watch?v=dQw4w9WgXcQ)[EmbedLink](https://vimeo.com/76979871)[EmbedLink](https://bsky.app/profile/bsky.app/post/3l6oveex3ii2l)[EmbedLink](https://mastodon.social/@Mastodon/116539053870420123)The link must be the sole content of its paragraph (a blank line before and after). YouTube and Vimeo render as lazy-loaded video players; Bluesky and Mastodon posts are fetched at build time and inlined as static cards. If a URL isn't supported or a post can't be fetched, the original link is kept as a regular link.
Using PagesCMS
What is PagesCMS?
PagesCMS is a free, open-source content management system that works directly with your GitHub repository. It gives you a visual interface for editing content — no need to open YAML or JSON files by hand.
When you save changes in PagesCMS, it commits them directly to your repository, which triggers a new build and deploy.
Setting it up
-
Go to app.pagescms.org and sign in with your GitHub account.
-
Select the repository where your Scaffold site lives.
-
PagesCMS reads the
.pages.ymlfile in the root of the repository and automatically sets up the editing interface.
That's it. No API keys, no database, no separate hosting.
What you can do in PagesCMS
-
Edit pages — Add, remove, and reorder sections. Each section type has a form with the relevant fields.
-
Write articles — Use the built-in Markdown editor with live preview.
-
Manage people — Add team members with headshots and roles.
-
Manage partners — Upload partner logos, set display order, and toggle featured status.
-
Manage resources — Add publications with download links and contributor cross-references.
-
Edit navigation — Change header and footer menus, add dropdown menus, link to internal pages or external URLs.
-
Update site settings — Change the site title, description, social links, analytics ID, and cookie consent copy.
-
Upload images — All images are stored in
src/assets/and automatically optimized at build time.
How images work
When you upload an image through PagesCMS, it gets committed to src/assets/ in your repository. At build time, Astro optimizes these images — resizing, converting to modern formats, and generating responsive srcset attributes.
Image paths in content files always start with /src/assets/. For example: /src/assets/team/headshot.jpg.
For developers
Project structure
src/├── assets/ Images and media├── components/│ ├── atoms/ Basic elements (Button, Link, Image)│ ├── molecules/ Combinations (Card, NavItem)│ ├── organisms/ Complex components (Navigation, Footer)│ └── sections/ Page section components├── content/ Content collections (YAML, JSON, Markdown)├── layouts/ Page layouts├── pages/ File-based routing├── styles/ Tailwind theme and global CSS└── utils/ Utility functionsComponents follow atomic design — atoms are the smallest building blocks, molecules combine atoms, organisms combine molecules, and sections are full-width page blocks.
Adding a new page
Create a new YAML file in src/content/pages/. The filename becomes the URL path (e.g., privacy-policy.yaml → /privacy-policy). A GitHub Action keeps filenames and permalink fields in sync automatically.
Customizing styles
Scaffold uses Tailwind CSS v4 with custom theme tokens defined in src/styles/:
-
colors.css— Color palette (primary, secondary, highlight, gray) -
typography.css— Font families, prose styling, heading scales -
breakpoints.css— Responsive breakpoints
Change the color palette in colors.css and the entire site updates.
Changing fonts
Fonts are configured in fonts.config.mjs at the repo root — split out from astro.config.mjs so you can change them without conflicting with template updates. The file is also protected on merge, so your font choices survive npm run update-from-scaffold.
Scaffold uses Bunny Fonts as the provider — a privacy-friendly CDN that mirrors Google Fonts' catalogue without third-party tracking. To change a font:
- Browse fonts.bunny.net and pick what you want.
- Open
fonts.config.mjsand update thenameandweightsfor the slot you want to change (--font-sans,--font-serif, or--font-mono). - Restart
npm run devso Astro re-fetches the new font.
Keep the cssVariable names as they are — --font-sans / --font-serif / --font-mono are referenced from typography, components, and Tailwind utilities. Just point them at different fonts.
If you want a different provider (Google Fonts, local files, etc.) see Astro's font docs.
API endpoints
Every content collection is automatically exposed as a JSON API:
-
/api/pages.json -
/api/articles.json -
/api/people.json -
/api/partners.json -
/api/resources.json
These are generated at build time and can be used by external tools or client-side JavaScript.
Staying up to date with Scaffold
Scaffold is designed to be forked. Once you've replaced the demo content with your own, you can still pull future Scaffold improvements (components, layouts, utils, tooling) without losing the work you've done.
Pulling updates
Add Scaffold as a remote (one-time):
git remote add template https://github.com/draftlab-org/scaffold.gitThen, whenever you want updates:
npm run update-from-scaffoldThat's it. The script handles --allow-unrelated-histories on the first merge, re-applies any deletions you've made in protected paths (so demo content doesn't reappear via modify/delete conflicts), and auto-removes any brand-new upstream files in src/content/, src/assets/, and public/ so our demo content stays out of your production site.
What's protected
Scaffold ships a .gitattributes file that marks these paths as downstream-wins on merge — your version is always kept:
src/content/**— all content collections (pages, articles, people, etc.)src/assets/**— uploaded images, logos, artworksrc/styles/**— your theme tokens, typography, component utilitiespublic/**— favicons, OG images, robots.txt, and anything else you've added therefonts.config.mjs— your font choices (see "Changing fonts" below)
Everything else merges normally. Real code conflicts get flagged like any merge, and the script stops so you can resolve them by hand.
New upstream files and content collections
When upstream adds a brand-new file in src/content/, src/assets/, or public/, the script removes it as part of the merge. Demo content shouldn't sneak into your production site, and you can always add your own files later.
Brand-new files in src/styles/ are not auto-removed — new stylesheets may be required by new components in the merge.
If Scaffold ships an entirely new content collection (a new directory under src/content/), you'll see a notice at the end of the run:
ℹ There are new content collections on Scaffold — check out https://scaffold.org to see what's newThe collection's schema arrives via src/content.config.ts (which isn't protected and merges in normally); the demo files in the new directory are removed. If you want to use the new collection, head to scaffold.org to see what it is, then add your own content under that directory.
If you forked before this update script existed
Git reads .gitattributes from your working tree at the start of a merge, so existing forks need a one-time bootstrap to land the protection rules and the script itself:
git fetch templategit checkout template/main -- .gitattributes scripts/scaffold-update.shgit commit -m "Adopt Scaffold update script"bash scripts/scaffold-update.shThen add the npm shortcut to your package.json scripts so you don't have to remember the bash path:
"update-from-scaffold": "bash scripts/scaffold-update.sh"After this, npm run update-from-scaffold is all you need.
Doing it without the script
If you'd rather invoke Git directly, the equivalent is:
git fetch templategit merge template/main # add --allow-unrelated-histories on first runYou'll be on your own for modify/delete conflict resolution and new-file review.