This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
# Development
npm run dev # Start dev server at localhost:4321
npm run build # Full production build (see note below)
npm run preview # Serve the production build locally
# Code quality
npm run lint # ESLint
npm run format:check # Prettier check
npm run format # Prettier auto-fix
# Data
npm run fetch-maps-stats # Fetch Google Maps stats into src/data/google-maps-stats.json
npm run sync # Sync Astro content collection typesNote on
npm run build: The full build pipeline isfetch-maps-stats && astro check && astro build && pagefind --site dist && cp -r dist/pagefind public/. Thepagefindstep indexes content for the client-side search feature — skipping it leaves/searchbroken. There is nonpm testscript; Playwright is installed but not yet wired up.
The package manager used in CI is pnpm. Either npm or pnpm works locally.
This is an Astro 6 static site (personal blog + portfolio) built on the AstroPaper theme, deployed to GitHub Pages and mirrored as a Docker image to GHCR.
| File | Purpose |
|---|---|
src/config.ts |
Single source of truth for site metadata (URL, author, pagination, timezone, feature flags) |
src/constants.ts |
Social links (SOCIALS), share links (SHARE_LINKS), and Giscus comment config (GISCUS) |
src/content.config.ts |
Zod schema for the blog content collection; defines every valid frontmatter field |
astro.config.ts |
Astro integrations (sitemap, React), Shiki code themes, remark plugins |
Blog posts live in src/data/blog/ as Markdown files. Required frontmatter: title, pubDatetime, description. Optional: modDatetime, tags, featured, draft, ogImage, canonicalURL, hideEditPost, timezone. Files prefixed with _ are ignored by the glob loader.
@/ resolves to src/ (configured in tsconfig.json). Use this everywhere — avoid relative imports that traverse ...
React components (src/components/*.tsx) are used only for interactive features: GoogleMapsStats.tsx, UnsplashStats.tsx, and Comments.tsx (Giscus). Everything else is .astro. Keep the React surface minimal.
Dynamic OG images are generated at build time via Satori + @resvg/resvg-js. The entry point is src/pages/og.png.ts, templates live in src/utils/og-templates/, and orchestration is in src/utils/generateOgImages.ts. @resvg/resvg-js is excluded from Vite's optimizeDeps because it's a native module.
Light/dark mode is driven by CSS variables defined in src/styles/global.css using @theme inline. Key variables: --color-accent, --color-background, --color-foreground, --color-border, --color-muted. The theme toggle runs from public/toggle-theme.js (vanilla JS, no framework).
Search is powered by Pagefind, which requires a completed astro build to generate the index under dist/pagefind/. The build script copies that index into public/pagefind/ so astro preview can serve it. The search UI is at src/pages/search.astro.
- GitHub Pages: triggered on push to
mainvia.github/workflows/astro.yml - Docker: multi-stage build (Node → Nginx) published to GHCR via
.github/workflows/docker-image.yml - Google Maps stats: updated on a schedule via
.github/workflows/update-google-maps-stats.yml, which runsscripts/update-maps-cache.mjsand commits the result tosrc/data/google-maps-stats.json
- ESLint disallows
console.logeverywhere exceptscripts/ - Prettier: 2-space indent, 80-char print width, double quotes, LF line endings,
es5trailing commas - Tailwind class order is enforced by
prettier-plugin-tailwindcssusingsrc/styles/global.cssas the stylesheet reference - Timezone for post dates defaults to
America/Vancouver(set insrc/config.ts); individual posts can override via thetimezonefrontmatter field