Published10 min read

Part 1 — Customizing This Astro Theme: Separating Identity, Narrative, and Datasets from Templates

A practical guide to cloning, running, and customizing this Astro 7 + Tailwind CSS v4 site for your own domain—without ever editing a single .astro template.

When I first built this site on Astro and Tailwind CSS, it started the way most personal engineering sites do: my name, domain, bio paragraphs, social links, and navigation items were scattered directly across a dozen .astro page templates, layout shells, and utility scripts.

That works fine when a repository only ever serves one person. It becomes a headache the moment you want to release the codebase as a clean, open-source starter theme—or even when you simply want to update your bio or domain metadata without grepping through layout components, Open Graph generators, RSS feeds, and JSON-LD blocks.

Over the past few days, we refactored the entire frontend architecture around a single rule: a user adopting this theme should be able to customize 100% of their identity, UI mechanics, personal narrative, articles, career timeline, and photography without ever opening an .astro file in src/pages/ or src/components/.

Here is how the configuration, content collections, and datasets are structured—and the exact checklist to make this site your own.


Architecture Overview: Configs, Content, and Datasets

Instead of mixing personal prose with presentation markup, the src/ directory is divided into four distinct responsibilities:

4. Static Build Outputs (dist/)

Static HTML Pages & Lightbox Gallery

1200x630 Satori Open Graph Cards (/og/*.png)

RSS, Atom, Sitemap XSL, robots.txt & JSON-LD

Machine-Readable Archives (/llms.txt & /llms-full.txt)

3. Pure Typed Datasets (src/data/)

experiences.ts

(Career Timeline)

recommendations.ts

(Peer Endorsements)

techTerms.ts

(Technical Acronym Dictionary)

2. Narrative & Markdown Collections (src/content/)

src/content/pages/*.md

(home, about, now, contact, blog, log, photography, tags)

src/content/blog/*.md

(Technical Articles)

src/content/log/*.md + CHANGELOG.md

(Status-Updates & Releases)

src/content/photography/*.md

(Photo Metadata & Backstories)

1. Configuration Layer (src/*.config.ts)

src/site.config.ts

(Identity, Author, Avatar, Socials)

src/theme.config.ts

(Logo, Nav, Pagination, Tooltips, OG, Footer)

src/content.config.ts

(Zod Content Schemas)

Why separate src/content/ from src/data/?

  • src/content/ holds editorial prose and Markdown collections managed by Astro’s Content Layer (pages, blog, log, photography). These files go through the Unified Remark/Rehype pipeline (syntax highlighting, build-time Mermaid diagrams, GitHub-style alerts, and automatic acronym tooltips).
  • src/data/ holds pure, strongly-typed TypeScript datasets (experiences.ts, recommendations.ts, techTerms.ts) that are queried, cross-referenced, or indexed across multiple routes at build time. Helper logic lives in src/utils/, keeping src/data/ 100% data-only.

Step 1: Local Prerequisites & Quickstart

To run and build the site locally, you only need Node.js >= 20 and Playwright’s Chromium binary (which rehype-mermaid uses at build time to compile Markdown mermaid code fences into responsive inline SVGs with zero client-side JavaScript).

# 1. Install dependencies from package-lock.json
npm ci

# 2. Install headless Chromium for build-time Mermaid SVG rendering
npx playwright install chromium

# 3. Start the local dev server at http://localhost:4321
npm run dev

When you are ready to test a full production build (including static HTML, responsive WebP/AVIF photography variants, and build-time Satori Open Graph PNG cards):

npm run build
npm run preview

Step 2: Configure Your Identity (src/site.config.ts)

Your first stop when customizing the site is src/site.config.ts. This file is the single source of truth for who owns the site:

export const siteConfig: SiteConfig = {
  site: {
    url: 'https://www.example.com',
    domain: 'example.com',
    title: 'Jane Doe',
    description:
      'Jane Doe — Systems & Cloud Architect. Notes on distributed systems, infrastructure automation, and photography.',
    keywords: ['Jane Doe', 'Cloud Architecture', 'Infrastructure', 'Linux', 'Terraform'],
    locale: 'en_US',
    defaultTheme: 'dark', // 'dark' | 'light' | 'system'
  },
  author: {
    name: 'Jane Doe',
    givenName: 'Jane',
    familyName: 'Doe',
    role: 'Systems & Cloud Architect',
    jobTitle: 'Principal Systems Architect',
    location: 'Tokyo',
    flagEmoji: '🇯🇵',
    email: 'jane@example.com',
    avatar: 'author.jpg',     // Small circular chip on blog & log cards
    aboutImage: 'author.jpg', // Large portrait on /about/ (can be a separate photo!)
    pronouns: 'she/her',
  },
  socials: {
    linkedin: { url: 'https://www.linkedin.com/in/jane-doe/', label: 'LinkedIn' },
    github:   { url: 'https://github.com/janedoe', label: 'GitHub' },
    gitlab:   { url: 'https://gitlab.com/janedoe', label: 'GitLab' },
    instagram:{ url: '', label: 'Instagram' }, // Leave empty ('') to hide automatically
  },
};

Updating src/site.config.ts automatically cascades across the entire build:

  • Astro Site URL: Feeds astro.config.mjs for canonical URLs and @astrojs/sitemap.
  • Default Post Author: src/content.config.ts defaults every blog post’s author frontmatter field to siteConfig.author.name.
  • Headshots: Drop your portrait into src/images/author.jpg (or configure two separate files in src/images/ for avatar and aboutImage). Astro’s asset pipeline automatically generates optimized AVIF and WebP variants.
  • Dynamic Endpoints: /rss.xml, /atom.xml, /robots.txt, /sitemap-style.xsl, /llms.txt, /llms-full.txt, /llms.json, and Schema.org JSON-LD blocks all pull your name, domain, role, and social links directly from siteConfig.

Step 3: Tune Theme & UI Mechanics (src/theme.config.ts)

Next, open src/theme.config.ts to customize how the interface behaves and looks:

  1. Header SVG Wordmark (logo): Replace the inline SVG paths or adjust viewBox, width, and height for the top navbar logo.
  2. Third-Party Integrations (integrations):
    • Formspree (formspreeEndpoint): Paste your Formspree form endpoint URL to activate the interactive contact forms on /contact/ and /about/#contact (or set to "" to disable).
    • Umami Analytics (analytics): Toggle enabled: true/false and set your script URL and websiteId.
  3. Navigation & Pagination (navigation, pagination): Configure top navbar links and item counts per page for the blog archive (olderBlogsCount), log stream (logCount), photo gallery (galleryCount), and homepage carousels (homepageCarouselCount).
  4. Open Graph Card Branding (openGraph): Customize the badge label and footer text rendered onto every 1200×630 PNG social card generated by Satori at /og/*.png.
  5. Automatic Technical Tooltips (tooltips):
    • enabled: true
    • autoLink: 'first' ('first' | 'all' | 'off') — When set to 'first', the build-time Rehype plugin (rehype-tech-tooltips.mjs) scans your Markdown articles in top-to-bottom reading order and automatically wraps only the first occurrence of any technical acronym found in src/data/techTerms.ts into an accessible glassmorphic tooltip card (both in light and dark mode), leaving subsequent mentions as clean plain text.
  6. Footer Toggles (footer): Customize the footer navigation columns, copyright notice, showPoweredBy badge, and showThemeAttribution toggle.

Step 4: Edit Page Titles, Descriptions & Personal Narrative (src/content/pages/)

Every top-level page (/ , /about/, /now/, /contact/, /blog/, /log/, /photography/, and /tags/) loads its pageTitle, meta description, headings, structured cards, and personal narrative prose from Markdown files in src/content/pages/:

FileRouteWhat You Customize Here
src/content/pages/home.md/Homepage pageTitle, description, Intro hero card (eyebrow, heading, two-column blurb, CTA link), and section headers for the Photo Roll, Blog, and Log carousels.
src/content/pages/about.md/about/About pageTitle, description, greeting line, Markdown body (your personal biography paragraphs), spoken languages badges (fluent, native, beginner), faq accordion groups & answers, and contact form prompt.
src/content/pages/now.md/now/Your /now snapshot (lastUpdated, introHtml, and nowSections cards for Location, Engineering Focus, Homelab, Offline life, and Reading). Supports inline <abbr> tags for technical tooltips.
src/content/pages/contact.md/contact/Contact pageTitle, description, heading, Markdown body (intro paragraph), timezone/response-time labels, and form header.
src/content/pages/blog.md/blog/Blog index pageTitle, description, and archive section heading/subtitle.
src/content/pages/log.md/log/Log stream pageTitle, description, header badge, heading, and subtitle.
src/content/pages/photography.md/photography/Photography gallery pageTitle and description.
src/content/pages/tags.md/tags/Topic & tag directory pageTitle, description, eyebrow, heading, and subtitle.

Because these files are validated by Zod in src/content.config.ts, Astro will immediately catch any missing or mistyped frontmatter fields during npm run dev or npm run build.


Step 5: Populate Your Datasets (src/data/)

Three TypeScript files in src/data/ power the interactive career timeline, peer recommendations, and technical dictionary:

  1. src/data/experiences.ts: Powers the Living CV timeline on /about/ and automatically links career roles into matching topic archive pages under /tags/. Each entry includes role, company, date, location, summary, highlights, and tags.
  2. src/data/recommendations.ts: Powers the symmetrical bento grid of peer recommendations on /about/#recommendations.
  3. src/data/techTerms.ts: Contains ~60 categorized technical definitions (Cloud, Networking, Storage, Security, Hardware, Virtualization, Architecture, DevOps, AI). Add your own domain acronyms here and they will automatically upgrade to interactive tooltips across your blog posts with zero client-side dictionary payload.

Step 6: Authoring Blog Posts, Logs & Photography

Once your identity and narrative pages are in place, day-to-day publishing happens entirely in Markdown:

1. Technical Articles (src/content/blog/*.md)

Create a new .md file in src/content/blog/:

---
title: "Designing Resilient Multi-Region Storage"
description: "Practical notes on replication topologies, RPO/RTO trade-offs, and automated failover testing."
pubDate: "Nov 03 2026"
tags: ["AWS", "Infrastructure", "Architecture"]
sectionNav: "h1 to h2"
---
  • No author line needed: Defaults automatically to siteConfig.author.name.
  • Floating Section Navigation (sectionNav): Adding sectionNav: "h1 to h2" (or true) automatically extracts your article’s headings at build time and populates the floating sub-navigation pill bar.
  • Rich Markdown Extensions:
    • GitHub Alerts: Use > [!NOTE], > [!TIP], > [!IMPORTANT], > [!WARNING], or > [!CAUTION].
    • Mermaid Diagrams: Standard ```mermaid fences compile at build time into theme-adaptive inline SVGs.
    • YouTube Facades: Use a ```youtube code fence with a video URL on line 1 and title on line 2 to render a zero-tracking poster facade that also promotes the post to a <VideoCardPoster /> in blog listings.

2. Operational Logs (src/content/log/*.md & CHANGELOG.md)

  • Status-Updates: Drop short operational field notes into src/content/log/YYYY-MM-DD-slug.md.
  • Changelogs: Maintain site release notes in the root CHANGELOG.md using Keep a Changelog headings (## [YYYY-MM-DD] - Title). The /log/ stream parses CHANGELOG.md dynamically and merges releases with your status-updates and photo rolls.

3. Photography Roll (src/images/photography/ & src/content/photography/)

  • Place high-resolution source .jpg photos in src/images/photography/ (e.g., 1.jpg) and create a matching src/content/photography/1.md file with id, filename, title, pubDate, category, location, tags, and the backstory in the Markdown body.
  • The build pipeline automatically generates responsive WebP thumbnails for the justified gallery grid, full-resolution WebP lightbox views, individual /photography/[id]/ field-note pages, and 1200×630 Open Graph cards.

Next Up: Deploying Your Static Build (or Replicating the AWS Stack)

Once npm run build succeeds locally with your customized config and content, you have a complete static site in ./dist/.

From here, the ./dist/ folder is ready for your deployment platform of choice—or, if you also want to replicate the multi-region AWS infrastructure behind this site, give Part 2 — Provisioning & Deploying the Multi-Region AWS Stack with Terraform and GitLab CI/CD a whirl.

Ed Stuurman

About the author

Ed Stuurman

Infrastructure and reliability engineer based in Japan. Passionate about resilient systems, bare-metal hardware, cloud automation with Terraform, and all things homelab.

Contact

Thoughts or questions on this article?

Found a bug, tested a similar setup, or want to discuss the details? Drop a message below.

Emails are delivered directly to ed@stuurman.com.