The blog post about this blog
Try it live (opens in a new tab)
/ 7 min read
Overview
This repo does two jobs. First, runs my personal site at lawsonhart.me. Second, exports as a public template without shipping private services, tokens, or hosting assumptions. You can see the repo at github.com/lawsonhart/blog-template.
The repo started from Astro and Astro Cactus. That base helped me ship early versions fast. Over time, the site moved far past the original template. I added new layouts, custom components, server routes, search indexing, optional integrations, and a lot of UI work that exists because I use the site every day.
That split defines the repo. I want one codebase. I also want a clean template. The project only works if both goals stay true.
What this codebase is built for
I wanted a site that feels closer to a product than a folder of Markdown files. That means good typography, fast search, clear content structure, and room for server routes when a feature needs them. It also means optional features should stay optional.
That last point matters most. A template should not ask you for my tokens, my private endpoints, or my hosting setup. The repo works because the public path stays simple and the site-only path stays isolated.
The core template-safe features
These parts stay in the exported template and should work for anyone who clones the repo.
1. Content collections for posts and notes
Posts and notes both use Astro Content Collections. Posts support tags, technologies, cover images, and drafts. Tags are normalized to lowercase and duplicates are removed. Technologies are also deduped in a case-insensitive way. Draft posts are filtered only in production. That keeps local writing flexible and production output clean.
The technologies field is small, though useful. It drives the tech icon row on post cards and gives each post a bit more structure without adding much authoring cost.
2. MDX and a useful markdown pipeline
I write in Markdown and MDX, though I still want structure and guardrails. The pipeline supports directive-based admonitions, heading ids with linked headings, external link hardening, inline image handling through a public asset prefix, and code blocks through Astro Expressive Code.
The goal is simple. Writing should feel fast. I should not spend time fighting the renderer for basic post features.
3. Search through Pagefind
Search runs on Pagefind. The index builds after the site build in scripts/pagefind.mjs. Search stays off in dev so the local loop stays quick. On the site, search results match the rest of the UI instead of looking like a third-party widget dropped into the page.
This is a good fit for a blog template because search stays client-side. There is no search service to run and no extra backend to explain.
4. Server-generated OG images
Posts get OG images from a route that renders them with Satori and Resvg. This is one of those features that seems minor until a link preview looks empty or generic. Once the route exists, every post feels more finished when shared.
5. Vercel deploy with a real local preview path
The site builds with output: "server". Production runs on Vercel. Local preview uses the Node adapter because @astrojs/vercel does not support astro preview the way a local prod preview needs. That is why bun preview swaps adapters, builds, and then starts the preview server.
That setup sounds small, though it removes a common point of friction. Template users get a predictable local preview story instead of a production-only deployment path.
What stays private, or is left out on purpose
The public template is opinionated. Anything tied to my own services, private tokens, or narrow site-specific behavior is a candidate for removal. That is not about hiding code. That is about shipping defaults that make sense for other people.
How the export works
template-excludes.txt lists files and paths that should not ship in the public snapshot. scripts/export-template.cjs copies the repo into a fresh folder and filters those paths out. .env files are always excluded as a second layer of protection.
There is also a sandbox flow so I can test template mode without moving files around by hand. That matters because template-safe code needs regular testing or the public path will drift.
Examples of site-only features
The exclude list changes as the site changes, though the pattern stays the same. Site-only features often include Spotify widgets, GitHub routes and stats, Umami analytics proxy routes, holiday components, comments UI, and some posts or project content that do not fit a generic starter.
None of those pieces are magic. They are simply poor defaults for a public template.
How the repo got here
The commit history shows a clear progression.
Phase 1. Make the site feel alive
The early work focused on product feel. Search went through several iterations before it felt native. Post cards picked up more useful detail like reading time and table-of-contents context. The about page stopped being a placeholder and became a real profile page.
Phase 2. Add integrations, then make them optional
This is where the repo picked up richer widgets, analytics, GitHub routes, comments experiments, and more server-side helpers. That work exposed the main template problem. Every integration adds value for me, though each one also raises the risk that the repo stops being portable.
That is where the line became obvious. A template should not depend on my infrastructure. The repo shifted toward soft imports and export filtering so private features stayed possible without turning the public repo into a setup checklist.
Phase 3. Make template-safe a hard requirement
The turning point was the tooling around template mode. I added a non-destructive sandbox, a real exporter, and build guardrails for optional files. After that, the repo stopped being only my site repo. It became a site repo with a template path that had to hold up on its own.
If you want to use this as a template
This is the path I expect most people to take.
1. Clone and install
Install Node 20 or newer. Install bun. Run bun install. The project uses bun for the normal local workflow, so that should happen first.
2. Update site identity
Edit src/site.config.ts and set the site url, title, author, and description. After that, update navigation links and socials so the header and profile areas match your own site.
3. Add content
Posts live under src/content/post/**. Notes live under src/content/note/**. Follow the frontmatter shape already used in the repo, especially for title, description, publishDate, tags, and technologies. Keeping those fields consistent makes the list pages and cards work without extra tweaking.
4. Use the normal validation flow
Run bun check for Astro type and content checks. Run bun run build for a production build plus Pagefind indexing. Run bun preview when you want a local production-like preview.
What might be missing
If you use the exported template snapshot, some features will be absent by design. Comments UI, Spotify widgets, analytics views, and similar site-only pieces are not part of the default export.
If you need one of those parts, reach out and I can point you in the right direction or help you pull over the right slice.
Email: me@lawsonhart.me