Skip to content
atsikhamPublic

About

tikho.me

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

tikho.me

Personal blog — platform engineering, Kubernetes, Go, and whatever else comes up. Live at https://tikho.me.

No framework, no build step. Vanilla HTML/CSS/JS frontend served from S3 + CloudFront, a small Python Lambda on API Gateway for likes, reads and comments, DynamoDB as the backing store. Posts are AsciiDoc or Markdown files — the parser runs in the browser.

how it works

The frontend fetches src/posts/manifest.json on load, then pulls each post file on demand. Likes, read counts and comments are kept in localStorage for instant feedback and work without a backend. When CONFIG.API_URL is set (injected by Terraform at deploy time) they also sync to DynamoDB so the counts are real across devices and users.

Posts get no-cache headers so a new post shows up immediately after deploy. JS/CSS assets get a one-year max-age and a CloudFront invalidation on every deploy handles busting them when something changes.

Comments are threaded with reply support. The comment tree is rendered client-side from a flat list of objects with an optional replyToId field.

structure

personal-website/
├── index.html
├── posts/                 # prerendered per-post entrypoints for SEO
├── scripts/
│   ├── generate-sitemap.mjs
│   └── prerender-posts.mjs
├── src/
│   ├── css/style.css
│   └── js/
│       ├── app.js           # UI logic
│       ├── storage.js       # localStorage + API abstraction
│       ├── pure.mjs         # pure helpers (also used by unit tests)
│       ├── config.js        # local dev fallback (API_URL left empty)
│       └── config.js.tftpl  # rendered by Terraform with the live API URL
├── src/posts/
│   ├── manifest.json        # post list + global config
│   ├── *.adoc / *.md        # post content
│   └── diagrams/            # SVG diagrams, one subfolder per post
├── lambda/
│   ├── index.py             # API handler — likes, reads, comments, replies
│   └── tests/
├── terraform/
│   ├── *.tf                 # S3, CloudFront, Lambda, API Gateway, DynamoDB
│   └── backend.hcl.example
├── tests/
│   ├── unit/                # parser and helper unit tests
│   └── smoke/               # smoke tests (browser test requires Chromium)
└── .github/workflows/       # CI runs on every push; deploy runs on main after tests pass

running locally

python3 -m http.server 8080

Has to be a real HTTP server — the site fetches post files over HTTP. src/js/config.js leaves API_URL empty so the site falls back to localStorage-only mode locally.

generating SEO artifacts

npm run gen:seo

This regenerates:

  • src/sitemap.xml

  • posts/{id}.html

Run it whenever you add/remove posts or change titles/excerpts.

tests

npm test               # frontend unit tests
npm run test:smoke:seo # dependency-free SEO tag checks
npm run test:smoke     # Playwright browser smoke test (requires Chromium)
npm run test:lambda    # Python Lambda tests

The Playwright smoke test requires Chromium. On a fresh machine run npx playwright install chromium once before npm run test:smoke. In restricted environments (corporate MITM / custom TLS) the Chromium download can fail; in that case the smoke test skips automatically.

adding a post

Drop a .adoc or .md file in src/posts/ and add an entry to src/posts/manifest.json:

{
  "id": "2",
  "file": "my-post.adoc",
  "title": "Post title",
  "excerpt": "One or two sentences shown on the card.",
  "authors": [{ "name": "Name", "initials": "FL", "url": "https://github.com/user" }],
  "date": "Apr 2, 2026",
  "tags": ["Kubernetes", "Platform Engineering"]
}

readTime is calculated automatically from word count — no need to set it.

To regenerate the SEO files after adding a post, run:

npm run gen:seo

infrastructure

Terraform in terraform/ provisions everything: S3 bucket, CloudFront distribution, Lambda function, API Gateway HTTP API, and two DynamoDB tables (blog_post_stats and blog_comments). It also renders src/js/config.js from the template so the deployed frontend always has the right API URL.

Remote state lives in a separate S3 bucket. See terraform/backend.hcl.example for the config shape.

deploy

Every push to main runs through CI and then syncs to S3 and invalidates CloudFront. Deploys are serialized with GitHub Actions concurrency so concurrent pushes queue up rather than stomping on each other.

Required repository secrets: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, S3_BUCKET, TF_STATE_BUCKET, TF_STATE_KEY, ACM_CERTIFICATE_ARN.

license

MIT — see LICENSE.

About

tikho.me

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages