From 22f482d89aab4aeb01826079d94836b5d53ec108 Mon Sep 17 00:00:00 2001 From: Jonathon Wright Date: Wed, 17 Jun 2026 16:56:46 +1000 Subject: [PATCH] =?UTF-8?q?M2:=20content=20collections=20=E2=80=94=20case?= =?UTF-8?q?=20studies,=20blog,=20RSS,=20tags,=20sitemap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- astro.config.mjs | 15 ++ package-lock.json | 201 +++++++++++++++++- package.json | 2 + src/components/Nav.astro | 3 +- src/components/PostList.astro | 85 ++++++++ src/components/ProjectCard.astro | 27 +-- src/content.config.ts | 39 ++++ src/content/blog/draft-placeholder.md | 11 + src/content/blog/init-gating-gpu-readiness.md | 52 +++++ src/content/blog/shipping-this-site.md | 40 ++++ src/content/projects/edge-ai-platform.md | 58 +++++ .../projects/global-infra-modernisation.md | 49 +++++ src/content/projects/gpu-as-code.md | 47 ++++ src/content/projects/iac-fleet-automation.md | 51 +++++ .../projects/self-hosted-ai-homelab.md | 51 +++++ src/data/projects.ts | 82 ------- src/lib/blog.ts | 21 ++ src/lib/reading.ts | 6 + src/pages/blog/[slug].astro | 88 ++++++++ src/pages/blog/index.astro | 52 +++++ src/pages/blog/tags/[tag].astro | 33 +++ src/pages/index.astro | 8 +- src/pages/projects/[slug].astro | 105 +++++++++ src/pages/projects/index.astro | 14 +- src/pages/rss.xml.ts | 21 ++ src/styles/global.css | 83 ++++++++ 26 files changed, 1139 insertions(+), 105 deletions(-) create mode 100644 src/components/PostList.astro create mode 100644 src/content.config.ts create mode 100644 src/content/blog/draft-placeholder.md create mode 100644 src/content/blog/init-gating-gpu-readiness.md create mode 100644 src/content/blog/shipping-this-site.md create mode 100644 src/content/projects/edge-ai-platform.md create mode 100644 src/content/projects/global-infra-modernisation.md create mode 100644 src/content/projects/gpu-as-code.md create mode 100644 src/content/projects/iac-fleet-automation.md create mode 100644 src/content/projects/self-hosted-ai-homelab.md delete mode 100644 src/data/projects.ts create mode 100644 src/lib/blog.ts create mode 100644 src/lib/reading.ts create mode 100644 src/pages/blog/[slug].astro create mode 100644 src/pages/blog/index.astro create mode 100644 src/pages/blog/tags/[tag].astro create mode 100644 src/pages/projects/[slug].astro create mode 100644 src/pages/rss.xml.ts diff --git a/astro.config.mjs b/astro.config.mjs index 0c443c4..5836e36 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,11 +1,26 @@ // @ts-check import { defineConfig } from "astro/config"; +import sitemap from "@astrojs/sitemap"; import tailwindcss from "@tailwindcss/vite"; // https://astro.build/config export default defineConfig({ site: "https://www.bztmon.com", + integrations: [sitemap()], + build: { + // Keep CSS in external files (no inlined diff --git a/src/components/ProjectCard.astro b/src/components/ProjectCard.astro index b28ec24..f37a4ed 100644 --- a/src/components/ProjectCard.astro +++ b/src/components/ProjectCard.astro @@ -1,30 +1,30 @@ --- -import type { Project } from "../data/projects"; +import type { CollectionEntry } from "astro:content"; interface Props { - project: Project; + entry: CollectionEntry<"projects">; } -const { project } = Astro.props; -const href = project.hasCaseStudy ? `/projects/${project.slug}` : undefined; -const Tag = href ? "a" : "article"; +const { entry } = Astro.props; +const p = entry.data; +const href = `/projects/${entry.id}`; --- - +
-

{project.title}

- {href && } +

{p.title}

+
-

{project.outcome}

-

{project.summary}

+

{p.outcome}

+

{p.summary}

    - {project.stack.map((s) =>
  • {s}
  • )} + {p.stack.map((s) =>
  • {s}
  • )}

- {project.role}·{project.period} + {p.role}·{p.period}

-
+ diff --git a/src/pages/blog/index.astro b/src/pages/blog/index.astro new file mode 100644 index 0000000..ce6fcaf --- /dev/null +++ b/src/pages/blog/index.astro @@ -0,0 +1,52 @@ +--- +import Layout from "../../layouts/Layout.astro"; +import Section from "../../components/Section.astro"; +import PostList from "../../components/PostList.astro"; +import { getPosts, allTags } from "../../lib/blog"; + +const posts = await getPosts(); +const tags = allTags(posts); +--- + + +
+

+ Lessons from edge Kubernetes, GPUs, and running infrastructure like it matters. +

+ + {tags.length > 0 && ( + + )} + + +
+
+ + diff --git a/src/pages/blog/tags/[tag].astro b/src/pages/blog/tags/[tag].astro new file mode 100644 index 0000000..cdfe308 --- /dev/null +++ b/src/pages/blog/tags/[tag].astro @@ -0,0 +1,33 @@ +--- +import Layout from "../../../layouts/Layout.astro"; +import Section from "../../../components/Section.astro"; +import PostList from "../../../components/PostList.astro"; +import { getPosts, allTags } from "../../../lib/blog"; +import type { GetStaticPaths } from "astro"; + +export const getStaticPaths = (async () => { + const posts = await getPosts(); + const tags = allTags(posts); + return tags.map((tag) => ({ + params: { tag }, + props: { tag, posts: posts.filter((p) => p.data.tags.includes(tag)) }, + })); +}) satisfies GetStaticPaths; + +const { tag, posts } = Astro.props; +--- + + +
+

+ ← All posts +

+ +
+
+ + diff --git a/src/pages/index.astro b/src/pages/index.astro index f16323e..da54114 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -7,7 +7,11 @@ import SkillGroup from "../components/SkillGroup.astro"; import ProjectCard from "../components/ProjectCard.astro"; import Contact from "../components/Contact.astro"; import { skills } from "../data/skills"; -import { featuredProjects } from "../data/projects"; +import { getCollection } from "astro:content"; + +const featuredProjects = (await getCollection("projects", (p) => p.data.featured)).sort( + (a, b) => a.data.order - b.data.order, +); --- @@ -25,7 +29,7 @@ import { featuredProjects } from "../data/projects";
- {featuredProjects.map((project) => )} + {featuredProjects.map((entry) => )}

All projects → diff --git a/src/pages/projects/[slug].astro b/src/pages/projects/[slug].astro new file mode 100644 index 0000000..d8da119 --- /dev/null +++ b/src/pages/projects/[slug].astro @@ -0,0 +1,105 @@ +--- +import Layout from "../../layouts/Layout.astro"; +import { getCollection, render } from "astro:content"; +import type { GetStaticPaths } from "astro"; + +export const getStaticPaths = (async () => { + const projects = await getCollection("projects"); + return projects.map((entry) => ({ params: { slug: entry.id }, props: { entry } })); +}) satisfies GetStaticPaths; + +const { entry } = Astro.props; +const p = entry.data; +const { Content } = await render(entry); +--- + + +

+
+ ← All projects + +
+

Case study

+

{p.title}

+

{p.outcome}

+ +

+ {p.role}·{p.period} +

+ +
    + {p.stack.map((s) =>
  • {s}
  • )} +
+ + {p.links && ( + + )} +
+ +
+ +
+
+
+ + + diff --git a/src/pages/projects/index.astro b/src/pages/projects/index.astro index 3ed7343..6f997a5 100644 --- a/src/pages/projects/index.astro +++ b/src/pages/projects/index.astro @@ -2,24 +2,28 @@ import Layout from "../../layouts/Layout.astro"; import Section from "../../components/Section.astro"; import ProjectCard from "../../components/ProjectCard.astro"; -import { projects } from "../../data/projects"; +import { getCollection } from "astro:content"; + +const projects = (await getCollection("projects")).sort( + (a, b) => a.data.order - b.data.order, +); ---

- Edge Kubernetes, GPU inference, self-hosted AI, and the automation that ties - it together. Full case studies are on the way. + Edge Kubernetes, GPU inference, self-hosted AI, and the automation that ties it + together — each with the problem, the design, and the outcome.

- {projects.map((project) => )} + {projects.map((entry) => )}