The reasoning behind every architectural decision in the Skeleton. This is the teaching, given away free.
Why this exists
Most people who want to own their platform face two doors. Rent a platform you do not control. Or hire someone to build one you do not understand. Both leave you dependent.
This Skeleton is the third door: a working, sovereign publishing platform with every decision explained so you understand what you own and why it works.
1. Why Astro
Astro is a static site framework. It takes your content (markdown files with structured data), runs it through templates at build time, and produces plain HTML files. No server. No database. No runtime. The output is a folder of files you can host anywhere.
This matters for sovereignty because:
- Your content lives in plain files on your machine, not in a vendor’s database.
- Your site can move to any host that serves static files. There is no vendor lock-in.
- Static sites are fast, secure (no server to attack), and cheap to host (often free).
Astro was chosen over other static generators because it supports typed content collections with Zod validation, which gives you build-time safety without a runtime cost.
2. Why typed content collections
Every post in your platform has a schema: a contract that defines what fields are required, what types they must be, and what values are valid.
title: z.string().min(1) // must exist, must not be empty
slug: slugSchema // must be kebab-case
status: publishStateSchema // must be "draft", "published", or "archived"
publishedAt: z.coerce.date() // must be a valid date
When you build the site, Astro validates every content file against this schema. A post with a missing title, a bad slug, or an invalid date will fail the build with a clear error message. Bad data never reaches production.
This prevents a class of problems that grow silently in untyped systems: missing fields, inconsistent formats, broken references. A schema catches them at the earliest possible point.
The schema lives at src/content/schemas/posts.ts. You can extend it with new fields (add a category enum, a readTime string, a heroImage path) and the validator will enforce the new contract immediately.
3. Why the draft gate
The function getPublished() in src/lib/content.ts is a query filter. Every page that displays content calls it instead of querying the collection directly. It returns only entries where status === "published", with one exception: in dev mode, drafts are also visible so you can preview them.
This single function prevents the most dangerous publishing mistake: accidentally shipping an unfinished draft to the public. It is used in:
- The homepage (recent posts)
- The posts index
- The post detail page route generation (
getStaticPaths) - The RSS feed
Because it gates every surface, a draft post does not generate a page, does not appear in any list, and does not enter the feed. The only way to make it public is to change its status to “published” and rebuild.
The draft gate is the single most valuable safety feature in this architecture.
4. Why owned RSS
Your RSS feed is generated at build time by src/pages/rss.xml.ts. It creates a standard XML feed from your published posts, sorted by date, and serves it from your domain.
This is your audience pipe. Anyone who subscribes to your feed connects directly to you, not through a platform’s algorithm or notification system. That connection is:
- Yours to maintain (it lives on your domain).
- Portable (RSS is an open standard any reader can consume).
- Impossible for a platform to cut (there is no platform in the middle).
If you move hosts, your feed URL stays the same as long as you keep your domain. If a reader switches feed apps, your feed still works. There is no middleman.
5. Why four dependencies
Every dependency is a point of failure, a thing that can be deprecated, and a surface that can change its pricing or terms. The Skeleton ships with four:
| Dependency | Purpose |
|---|---|
| astro | The framework: static site generation, content collections, routing |
| @astrojs/mdx | MDX support for rich content (components inside markdown) |
| @astrojs/rss | RSS feed generation |
| @astrojs/sitemap | Automatic sitemap for search engines |
No runtime dependencies. No database. No CMS. No auth layer. Four things to audit, four things to update, four things that can break. Everything else is standard web platform (HTML, CSS, JavaScript).
6. Why this file structure
src/
content/
posts/ # Your content lives here, as markdown files
schemas/ # The validation rules for your content
layouts/ # The page shell (header, footer, structure)
lib/ # Shared utilities (the draft gate lives here)
pages/ # Routes: each file becomes a URL
posts/ # The posts list and detail pages
rss.xml.ts # The RSS feed
styles/
tokens.css # Design tokens (colors, fonts, spacing)
content.config.ts # Registers your content collections
Content and code are separated. Your writing lives in content/posts/ as plain markdown files you can read, edit, and move without touching any code. The code that turns those files into a website lives everywhere else.
This separation means your archive is portable. If you ever outgrow Astro or want to move to a different system, your content is just markdown files with YAML frontmatter. Any static site generator can read them.
7. Patterns the Skeleton teaches (not pre-built)
The Skeleton ships with one chamber and one content type. The architecture it is built on supports more. These patterns are explained here so you understand what is possible and what the Forge (done-with-you build service) would construct.
Cross-references
Astro’s reference() function lets one collection point to another. For example, a post could reference a list of tags that are themselves a validated collection, or an author collection, or a sources collection with citation data.
In the Skeleton, the posts schema uses a simple tags array of strings. To upgrade to typed cross-references:
- Create a new collection (e.g.,
topics/) with its own schema. - In the posts schema, change
tagsfromz.array(z.string())toz.array(reference("topics")). - Each post’s
tagsfield now must reference valid entries in the topics collection. The build validates this.
This pattern scales to a full knowledge graph: posts reference topics, topics reference related topics, posts reference sources. The validation ensures every reference points to something real.
Multiple chambers (the multi-world system)
The Skeleton has one visual identity. The architecture supports multiple visually distinct editorial spaces on a single codebase. Each chamber can have its own color palette, typography, layout patterns, and editorial voice, while sharing the same content system and build pipeline.
To add a second chamber:
- Add a new content collection with its own schema.
- Add new pages under a new route (e.g.,
src/pages/stories/). - Add a chamber prop to the layout and style each chamber differently using CSS custom properties.
The multi-world system is the core architectural difference between a blog and an editorial platform. It is taught here, not pre-built, because wiring and theming chambers to a specific person’s brand is labor (the Forge).
8. The sovereignty test
When evaluating any platform, ask four questions:
- Do you own the code? Not a login. The actual files, on your machine.
- Do you own the audience? Through pipes you control, in formats you can export.
- Do you own the archive? In plain, portable files that outlast any platform.
- Do you own the terms? No algorithm, no pricing change, no policy can erase you.
This Skeleton passes all four. If yours does not, you are renting.