How This Site Is Built

A technical breakdown of the stack, architecture, and the reasoning behind each choice.

I built this site the same way I approach any data platform: start with clear requirements, pick the simplest tools that meet them, own the infrastructure, and automate everything that can be automated. No vendor lock-in. No magic. Every piece is inspectable and replaceable.

The Requirements

Before picking any technology, I defined what I actually needed:

  • Fast: Sub-second page loads. No spinners, no hydration delays, no layout shift.
  • Cheap: Running costs under $2/month. A blog should not cost more than a coffee.
  • Secure: No server to patch. No database to protect. Minimal attack surface.
  • Portable: Content lives in Markdown files. If the framework dies tomorrow, the content survives.
  • Automated: Push to main, site deploys. No manual steps, no SSH, no FTP.

The Stack

Astro (Static Site Generator)

Astro generates plain HTML at build time. No JavaScript ships to the browser unless explicitly needed. This is the opposite of frameworks like Next.js or Gatsby, which ship a full React runtime even for a blog post that is just text.

Why not Next.js? Next.js is excellent for applications. A blog is not an application. It is a collection of documents. Astro treats content as a first-class citizen with its Content Collections API, which gives me type-safe frontmatter validation, automatic slug generation, and zero client-side JavaScript by default.

Why not Hugo or Jekyll? Hugo is fast but templating in Go is painful. Jekyll is aging and Ruby is one more runtime to manage. Astro uses standard HTML/CSS/JS with a component model I already know from React. The learning curve was nearly zero.

Tailwind CSS (Styling)

Tailwind with the Typography plugin. Utility classes mean I never context-switch between HTML and CSS files. The Typography plugin handles all the prose styling (article body, headings, code blocks, blockquotes) with a single prose class.

Markdown + MDX (Content)

Every article is a Markdown file with YAML frontmatter. This is deliberate. Markdown is the most portable content format that exists. If I ever switch to a different static site generator, CMS, or publishing platform, the content moves with me unchanged.

MDX support is available for articles that need interactive components, but most articles are plain Markdown. Simplicity is the default; complexity is opt-in.

The Infrastructure

S3 + CloudFront (Hosting)

The site is a collection of static HTML, CSS, and JS files stored in a private S3 bucket and served through CloudFront, AWS's global CDN. CloudFront uses Origin Access Control (OAC) to access the bucket, which means the bucket itself is completely private. No public access, no website hosting mode, no open permissions.

Why not Vercel or Netlify? Three reasons:

  1. Ownership: I control the infrastructure. If Vercel changes their pricing, limits, or terms, I am not affected. The site runs on standard AWS services that have been stable for over a decade.
  2. Cost: CloudFront's free tier covers 1TB of data transfer and 10 million requests per month. For a personal blog, the monthly bill is effectively $0.50 for Route 53 hosting plus a few cents for S3 storage.
  3. Learning: I work with AWS professionally. Running my own infrastructure on the same platform keeps my skills sharp in a way that clicking "Deploy" on Vercel does not.

AWS CDK with Python (Infrastructure as Code)

The entire infrastructure is defined in a single CDK stack: S3 bucket, CloudFront distributions, ACM certificates, Route 53 hosted zones, IAM roles, and security headers. One cdk deploy creates everything from scratch. One cdk destroy tears it all down.

Why CDK over Terraform? CDK generates CloudFormation, which means AWS manages the state. No state file to store, no state locking to configure, no S3 backend to set up. For a single-developer project, this removes an entire category of operational burden.

CloudFront Functions (Edge Logic)

Two CloudFront Functions handle edge logic without a server:

  • URL Rewriting: Astro generates /about/index.html but users visit /about. A viewer-request function rewrites the URI before S3 is contacted.
  • Domain Redirect: thedatapraxis.com 301-redirects to vikaspratapsingh.com entirely at the edge. The origin is never contacted.

Security Headers

CloudFront's Response Headers Policy adds security headers to every response:

  • Content-Security-Policy: Restricts scripts, styles, fonts, and frames to trusted sources only.
  • Strict-Transport-Security: Forces HTTPS with a 1-year max-age, including subdomains, with preload.
  • X-Frame-Options: DENY. This site cannot be embedded in iframes.
  • Permissions-Policy: Blocks camera, microphone, geolocation, and payment APIs. A blog needs none of these.

The Deployment Pipeline

GitHub Actions with OIDC (No Stored Secrets)

When I push to main, GitHub Actions builds the site and deploys it to S3. The workflow uses OIDC federation to assume an IAM role, which means there are no long-lived AWS access keys stored as GitHub secrets. The role is scoped to exactly three actions: list/read/write S3 objects and create CloudFront invalidations. Nothing else.

The deploy workflow is 47 lines of YAML. It does four things: checkout, build, sync to S3, invalidate CloudFront. No build cache tricks, no preview deployments, no complexity beyond what is needed.

What It Costs

Service Monthly Cost
Route 53 (2 hosted zones) $1.00
S3 storage ~$0.01
CloudFront (free tier) $0.00
ACM certificates $0.00
Total ~$1.01/month

For comparison, a basic Squarespace plan costs $16/month. A WordPress site on managed hosting runs $25-50/month. This site costs roughly $12 per year.

Design Decisions

  • Typography: Source Serif 4 for headings (authority, readability), Inter for body text (clean, neutral). Both loaded from Google Fonts.
  • Color: Deep teal (#0d9488) as the accent. Professional without being corporate. Distinctive without being distracting.
  • Content width: 680px max for article body. This keeps line length between 60-80 characters, which is the optimal range for reading comprehension.
  • Dark mode: Respects system preference with manual toggle. Most developers read in dark mode; most executives read in light mode. Supporting both is not optional.
  • No analytics: No Google Analytics, no tracking pixels, no cookies. If I need to know how many people visit, I will check CloudFront access logs.

The Source Code

The full source is available at github.com/vikingh27/vps-blog. The infrastructure code, the site code, the content, and the deployment pipeline are all in one repository.