Back to Blog
GeneralAI

Hello World

Chase Dovey
7 min read

Who I Am

I'm Chase Dovey, an AI and cloud engineer based in Houston, Texas. I came up through systems administration, moved into cloud engineering, and now spend most of my time building/securing scalable infrastructure and intelligent systems. My work sits at the intersection of cloud platforms and artificial intelligence. I like building things that are modular, maintainable, and well-documented.

This blog is where I write about what I'm learning, building, and thinking about.

What I Write About

Every post on this blog is tagged with one or more of the following categories, which align with the set of topics I cover, and it updates automatically as I expand into new areas:

  • AI - AI engineering, LLM integrations, and agentic systems
  • Architecture - System design, platform engineering, and scalable infrastructure patterns
  • Deep Learning - Neural network internals, model training, and the math behind modern ML
  • General - Meta posts, announcements, and topics that span multiple categories
  • Python - Python development, libraries, and tooling
  • Security - Application security, prompt injection, threat modeling, and defensive engineering

If you're here for a specific topic, the blog index lets you filter by tag.

How This Site Is Built

I built this site/blog using a two-repo architecture, and it's a good example of the kind of engineering I write about. Let me walk through it in detail.

Architecture Overview

The system is split into two independent GitHub repositories with a clear separation of concerns:

Infrastructure

Website Repo

Content Repo

repository_dispatch

git clone

unified pipeline

next build

deploy

custom domain

posts/*.md

tags.json

lib/blog.ts

Next.js App

Static HTML

GitHub Pages

cdovey.dev

RepoPurposeContains
mrcloudchase-blogContentMarkdown posts, tag definitions, rebuild trigger workflow
mrcloudchase.github.ioApplicationNext.js site, Markdown processing pipeline, components, CI/CD

The content repo has no build system, no dependencies, and no configuration beyond a single GitHub Actions workflow. It's just Markdown. The website repo is a full TypeScript application with strict type checking, linting, and a multi-stage build pipeline.

The Content Pipeline

Every blog post starts as a Markdown file with YAML frontmatter:

---
title: "Post Title"
date: "2026-03-02"
excerpt: "Short description for cards and SEO."
author: "Chase Dovey"
tags: ["AI", "Architecture"]
draft: false
---

## Post content starts here...

At build time, the website clones the content repo into a gitignored content/blog/ directory. From there, lib/blog.ts processes every .md file through a pipeline that parses the frontmatter, transforms the Markdown into HTML, and generates static pages.

The Markdown processing uses the unified ecosystem, a pipeline of parsers and transformers that convert Markdown to an abstract syntax tree, transform it, and serialize it to HTML:

raw .md file

remark-parse

Parse Markdown into AST

remark-gfm

Add tables, strikethrough, task lists, autolinks

remark-rehype

Convert Markdown AST to HTML AST

(preserves raw HTML)

rehype-raw

Process raw HTML fragments into proper AST nodes

rehype-mermaid

Render Mermaid blocks to inline SVGs

(headless Chromium via Playwright)

rehype-stringify

Serialize HTML AST to string

contentHtml via dangerouslySetInnerHTML

Each plugin in the chain does one thing. remark-parse tokenizes Markdown into an AST (Abstract Syntax Tree). remark-gfm extends it with GitHub Flavored Markdown features. remark-rehype bridges the Markdown AST to an HTML AST. rehype-raw handles inline HTML that was passed through. rehype-mermaid finds fenced code blocks tagged as mermaid and renders them to inline SVGs using a headless Chromium instance via Playwright, so diagrams are baked into the HTML at build time, not rendered client-side. rehype-stringify serializes the final AST to an HTML string.

The resulting HTML is injected into the page component via dangerouslySetInnerHTML (a React anti-pattern that's necessary here because the HTML is generated from trusted Markdown content) and styled by a .prose-blog CSS class that applies the site's terminal aesthetic to all standard HTML elements.

The CI/CD Pipeline

Two workflows coordinate the build and deploy cycle:

GitHub PagesGitHub ActionsWebsite RepoBlog RepoAuthorGitHub PagesGitHub ActionsWebsite RepoBlog RepoAuthorCheckout website codegit clone blog repo → content/blog/npm ciInstall Playwright Chromiumtsc --noEmit (type check)eslint (lint)next build (generates static HTML)Generate sitemapgit push (new post)repository_dispatch: blog-content-updatedTrigger deploy workflowDeploy out/ directorySite live at cdovey.dev

The website also rebuilds on direct pushes to its own main branch and on a daily cron schedule. The cron catches anything that might have been missed, like updated GitHub project data that's fetched from the API at build time.

The Tag System

Tags aren't freeform strings. They're validated at build time against a central definition. The content repo has a tags.json file at its root that defines every valid tag:

{
  "tags": [
    { "name": "AI", "description": "AI engineering, LLM integrations, and agentic systems" },
    { "name": "Security", "description": "Application security, prompt injection, ..." }
  ]
}

When the site builds, lib/blog.ts loads this file and does two things:

  1. Validates every post's frontmatter tags against the defined set. Any undefined tag produces a build warning
  2. Injects the full tag catalog into posts that contain a <!-- TAG_CATALOG --> marker, like this one

This means the "What I Write About" section above is generated at build time from tags.json. I add a tag to the JSON, push, and it appears here on the next deploy. No manual updates to blog posts required.

On the website side, app/blog/page.tsx aggregates all tags from published posts into a deduplicated, sorted list and passes them to a client-side BlogPostGrid component that renders interactive tag filter pills. Clicking a tag filters the post grid to show only matching posts.

The Stack

LayerTechnologyDetails
FrameworkNext.js 16App Router, output: 'export' (fully static, no Node.js server)
LanguageTypeScript 5.9Strict mode, path aliases (@/*)
StylingTailwind CSS 4Custom dark terminal theme with neon/cyber/surface color palettes
Markdownunified ecosystemremark-parse, remark-gfm, remark-rehype, rehype-raw, rehype-mermaid, rehype-stringify
DiagramsMermaid + PlaywrightServer-side SVG rendering via headless Chromium at build time
HostingGitHub PagesCustom domain at cdovey.dev, HTTPS via GitHub
CI/CDGitHub ActionsType check → lint → build → deploy on every push and repository_dispatch
RuntimeNode.js 22 LTSBuild-time only, no runtime server

Why Two Repos?

Separating content from code gives me a clean boundary:

  • Publish a post without touching the website codebase
  • Refactor the site without content commits cluttering the history
  • Different CI concerns - the content repo only needs to fire a dispatch event; the website repo runs type checking, linting, and a full Next.js build
  • Different access patterns - the content repo is just Markdown that any editor can write to; the website repo is a TypeScript application with strict tooling

The tradeoff is coordination. When both repos have changes, the website must be pushed first so that the blog push (which triggers a rebuild) picks up the latest site code. That ordering is enforced by convention, not automation, but it's a small price for the separation.

The Aesthetic

The terminal theme is intentional. I spend most of my day in a terminal, so it felt right to build a site that reflects that. The design system uses three custom color palettes (neon green, cyber cyan, and surface dark grays) with a purple accent. Headings and code use JetBrains Mono. Body text uses Inter. Everything sits on dark backgrounds with subtle borders and glow effects.

The CSS is built on Tailwind CSS 4 with a set of reusable component classes (terminal-card, terminal-window, tag-pill, prose-blog) defined in globals.css using Tailwind's @layer components. The .prose-blog class handles all Markdown-rendered content (headings, links, code blocks, tables, blockquotes, and Mermaid SVGs) so blog posts get consistent styling without per-element class injection.

What's Next

Check out the blog to see what I've published so far, take a look at my projects, or connect with me on GitHub.

$ echo "Let's build something."
Let's build something.