Skip to content
5 min read

Why I Built a Blog in 2026

The actual reasons a cloud engineer builds a static blog from scratch instead of using an existing platform. Spoiler — it is mostly about control.

Why I Built a Blog in 2026

Everyone asks the same question: "Why not just use Substack? Ghost? Medium?"

Fair question. Here's the real answer.

Control over the stack

I'm a cloud engineer. I have strong opinions about infrastructure.

A platform gives you a URL and a CMS. That's fine. But it also gives you:

  • A tracking pixel you didn't ask for
  • A dependency on their CDN configuration
  • No control over cache headers
  • Zero ability to add custom response headers

My setup (plain Terraform, not CDK — I wanted the state file, not an abstraction on top of it):

# infra/terraform/cloudfront.tf
resource "aws_cloudfront_distribution" "site" {
  is_ipv6_enabled = true
  http_version    = "http2and3"
  price_class     = var.price_class # PriceClass_200 — includes India edge locations

  origin {
    domain_name              = aws_s3_bucket.site.bucket_regional_domain_name
    origin_access_control_id = aws_cloudfront_origin_access_control.site.id
  }

  default_cache_behavior {
    viewer_protocol_policy     = "redirect-to-https"
    compress                   = true
    response_headers_policy_id = aws_cloudfront_response_headers_policy.security.id
  }
}

Cost

This blog costs approximately $0.50–$1.00 per month at zero traffic.

That's:

  • S3: ~$0.023/GB stored — negligible for HTML + fonts
  • CloudFront: first 1TB/month free, then $0.0085/GB in India region
  • Route 53: $0.50/hosted zone/month — that's the biggest line item

The build is the product

Shipping the blog is the content, at least for the first few months.

Writing about how the site is built while building it creates a feedback loop: I can't cut corners on the infrastructure if I'm going to write about it.

Security defaults I care about

Every response gets:

Strict-Transport-Security: max-age=63072000; includeSubDomains
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), interest-cohort=()

No CDN fonts. No analytics scripts. No cookies at all in v1. I'll add a cookieless analytics option later if I want numbers.

Content-Security-Policy is still on the TODO list, not a header I can claim yet. React Router inlines a hydration script and the theme-toggle script inlines too, so a strict policy needs their SHA-256 hashes before it can ship without breaking the site.

Performance targets

Lighthouse ≥ 95 across all four categories. CLS = 0.

The only JavaScript served is the React hydration bundle and the theme toggle. Both are small. Fonts are self-hosted via @fontsource, so there are no third-party network requests.

What's the catch?

Authoring is slightly more friction than a hosted platform. To publish a post:

  1. Write an .mdx file in content/posts/
  2. Run infra/terraform/scripts/deploy.sh — builds, syncs to S3 with the right cache headers, invalidates CloudFront

That's two steps and about a minute, no CI involved yet. A GitHub Actions deploy role is already in the Terraform (github_oidc.tf) for whenever I wire the workflow up — I've shipped worse deploy pipelines at real jobs even with that in place.