M2: content collections — case studies, blog, RSS, tags, sitemap

Projects + blog as schema-validated content collections; structured case
studies (problem/design/outcome), blog with tag pages, reading time, RSS
feed (drafts excluded), sitemap, and Shiki dual-theme code highlighting.
This commit is contained in:
2026-06-17 16:56:46 +10:00
parent 720d579386
commit 22f482d89a
26 changed files with 1139 additions and 105 deletions
+39
View File
@@ -0,0 +1,39 @@
import { defineCollection } from "astro:content";
import { z } from "astro:schema";
import { glob } from "astro/loaders";
// Case studies. Adding a project = one .md file in src/content/projects/.
const projects = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/projects" }),
schema: z.object({
title: z.string(),
// The one-line "so what" — shown on cards and the case-study header.
outcome: z.string(),
summary: z.string(),
role: z.string(),
period: z.string(),
stack: z.array(z.string()),
featured: z.boolean().default(false),
// Lower sorts first on the index + homepage.
order: z.number().default(50),
// Optional diagram key → rendered by the Diagram component (M3).
diagram: z.string().optional(),
links: z
.array(z.object({ label: z.string(), href: z.string().url() }))
.optional(),
}),
});
// Blog — write-only, schema-validated, pipeline-publishable.
const blog = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
schema: z.object({
title: z.string(),
date: z.coerce.date(),
summary: z.string(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});
export const collections = { projects, blog };