# How This Blog Runs for $0 a Month

> No VPS, no database, no CMS bill. This site is a git repo that compiles to static files — search index, social cards, and fonts included — and Cloudflare Pages serves it for free. Here's the whole pipeline, with the actual configs.

URL: https://harshith.in/blog/how-this-blog-runs-for-0/
Author: Harshith Sunku (Staff Engineer) — https://harshith.in/about/
Tags: web, systems, tools
Series: [Behind This Site](https://harshith.in/series/behind-this-site/) (part 1 of 1)

People assume a blog with full-text search, per-post social cards, RSS,
and a hundred-ish pages needs a server somewhere. This one has none. The
entire running cost is the domain registration — roughly the price of two
coffees a year — and everything else is $0. Not "free tier that becomes
$40/month when you get traffic" free. Actually free, by construction.

The trick isn't a discount. It's an architectural decision: **nothing
happens at request time**. If no code runs when you load a page, there's
nothing to bill.

## The pipeline

The whole system is one directed graph, and every arrow fires at build
time:

*[Diagram: Everything fires at build time, Cloudflare Pages serves the result]*

Posts are Markdown files in `src/content/blog/` — one file per URL. There
is no CMS and no database; the git history *is* the edit history, and a
`git revert` is my rollback story. Astro reads the collection at build
time, type-checks the frontmatter against a schema, and emits plain HTML.

## Hosting: Cloudflare Pages

Cloudflare Pages watches the GitHub repo. Every push to `main` triggers a
build (`astro build`, about a minute) and atomically swaps the deployment.
The free plan serves static assets with **unmetered bandwidth** from their
edge — the same CDN story I dug into in
[the HTTP caching contract](https://harshith.in/blog/http-caching-contract/), except here
every response is cacheable forever because nothing is dynamic. TLS is
included; the [handshake](https://harshith.in/blog/tls-handshake-mechanics/) terminates at
their edge, not on any machine I pay for.

The limits that matter on the free plan: 500 builds per month (I use
maybe 30) and a 25 MiB per-file cap (largest file here is a font subset,
~50 KiB). Traffic spikes are Cloudflare's problem, which is exactly where
I want that problem to live.

## Search without a server

Full-text search is the feature everyone assumes needs a backend. It
doesn't. [Pagefind](https://pagefind.app) runs *after* the build, crawls
`dist/`, and writes a chunked, compressed index as static files:

```json
"build": "astro build && pagefind --site dist"
```

The browser lazy-loads only the index fragments a query touches — a
search costs a few tens of kilobytes, not a round-trip to a search
service. The index is scoped with `data-pagefind-body` so navigation
chrome doesn't pollute results. Total infrastructure: zero.

## Social cards, fonts, CI — all build-time

**OG images.** Every post gets a 1200×630 social card at `/og/<slug>.png`,
rendered during the build by an Astro endpoint that feeds an SVG template
through sharp. When a post's title changes, the next build regenerates its
card. No image service, no on-demand rendering λ.

**Fonts.** The three typefaces are self-hosted woff2 files with
`@font-face` and preload hints — no Google Fonts request, no third party
in the critical path, and one less DNS lookup (a cost I've
[measured before](https://harshith.in/blog/dns-resolution-path/)).

**CI.** GitHub Actions runs `tsc --noEmit` and a full build on every
push. Free for public repos. It's the reviewer that never gets tired: a
broken frontmatter field or a dead import fails the check before
Cloudflare ever sees the commit.

## The bill

| Component | Provider | Cost |
|---|---|---|
| Hosting + CDN + TLS | Cloudflare Pages | $0 |
| Search | Pagefind (static) | $0 |
| Social cards | sharp at build time | $0 |
| CI | GitHub Actions | $0 |
| Analytics | none (deliberately) | $0 |
| Domain | registrar | ~$10/yr |

The domain is the only line item, and it's the one thing that can't be
static.

## What this architecture costs you instead

Honesty section: you pay in constraints, not dollars. No comments without
a third-party embed. No personalization, ever. Content changes require a
build (~90 seconds from push to live), so this is the wrong shape for
anything real-time. And build-time work scales with content — at
thousands of posts, the OG-image step would dominate and I'd need to cache
between builds.

For a technical blog, none of those bite. The failure mode of a $6 VPS is
a 3 AM page; the failure mode of this stack is that I wait a minute for a
deploy.

## Takeaways

- **Move all computation to build time.** If nothing runs at request
  time, hosting is free and outages need Cloudflare-sized incompetence.
- Full-text search does not require a server — Pagefind ships a static,
  lazily-loaded index that costs kilobytes per query.
- Social cards, fonts, sitemaps, RSS: all derivable from content at build
  time. Derive them; don't serve them.
- Git as CMS gives you review, rollback, and history for free — and lets
  CI reject broken content before it deploys.
- The next post in this series covers where the *rest* of my
  infrastructure lives: [the homelab](https://harshith.in/series/homelab/).
