MindfullyEmbedded is my personal tech blog, where I include articles, a project portfolio, professional bio, client success stories, training, consulting, and independent contract work information. I kept the frameworks at a minimum and the tech stack is HTML/CSS/(vanilla)JS with Cloudflare hosting and uses of available Cloudflare tool APIs (e.g., Turnstile, Pages, etc.).
- Article-first homepage and generated article pages
- Bio page with inline PDF resume viewer + download CTA
- Portfolio shelf with project detail pages and top-centered hero media
- Success stories page with anonymized measurable outcomes
- Client services pages (training, consulting, project contracts) with secure quote-request forms
- Section-specific parallax backgrounds
- Anonymous likes/dislikes on article and project pages
- Anonymous comments with one-level replies, optional pseudonym, and safe auto-generated fallback names
- Admin moderation dashboard with ban controls and audit-friendly APIs
- Install dependencies:
npm install
- Build static pages:
npm run build
- Run local static server:
npm start
- Open:
http://localhost:3000
Note: node server.js serves static files only. Cloudflare Functions APIs run when deployed (or via wrangler pages dev).
- Add publishable Markdown under
content/articles/. - Keep drafts in
content/drafts/(ignored by current build flow). - Required frontmatter keys:
titleslugdatesummarytagsreadTimepublished
- Run
npm run build.
Generated files:
public/articles/generated/*.htmlpublic/articles/articles.jsonpublic/articles/index.htmlpublic/feed.xml
- Feed URL:
/feed.xml - Includes: published articles only (
published: true) - Item payload:
<description>= article summary<content:encoded>= full article HTML<category>= mapped from article tags
- GUID strategy: canonical article URL (
isPermaLink="true") - Canonical base URL:
https://mindfullyembedded.com - Discovery:
- RSS autodiscovery
<link rel="alternate" ...>is added on article pages - Visible “Subscribe via RSS” CTA appears on
/articles/
- RSS autodiscovery
- Source of truth:
templates/footer.html - Build-time injector:
scripts/apply-templates.js
wrangler d1 create mindfully-embedded-blog-db
wrangler d1 execute mindfully-embedded-blog-db --file=db/schema.sqlwrangler kv namespace create RATE_LIMITS- Replace placeholder D1 database ID
- Replace KV namespace ID
- Set
ALLOWED_ORIGINS - Optionally set
ADMIN_IP_ALLOWLIST
You can copy .dev.vars.example to .dev.vars for local wrangler pages dev secrets.
wrangler secret put APP_SIGNING_SECRET
wrangler secret put IP_HASH_SALT
wrangler secret put TURNSTILE_SECRET_KEY
wrangler secret put ADMIN_SERVICE_TOKENSet meta tag content on pages using engagement widget:
<meta name="meb-turnstile-site-key" content="YOUR_TURNSTILE_SITE_KEY" />- Build command:
npm run build - Output directory:
public
- Signed write-session cookie:
HttpOnly,Secure,SameSite=Strict, 24h TTL - Session binding to IP-hash + UA-hash
- Strict origin allowlist for write endpoints
- Turnstile required for comment/reply submission
- Rate limiting via KV + ban checks via D1
- Ban model: IP-hash and subnet-hash support
- Admin APIs gated by Cloudflare Access identity + service token + optional IP allowlist
- Formatter: Prettier
- Linters: ESLint (JS), Stylelint (CSS), HTMLHint (HTML)
- Unit tests: Vitest + jsdom
- Local hooks: Husky + lint-staged
- CI: GitHub Actions quality and security workflows
Useful commands:
npm run format:check
npm run lint
npm run test
npm run qualitySecurity scanning commands:
npm run security:deps
npm run security:sast
npm run security:secretsGET /api/admin/commentsPOST /api/admin/comments/:idwith{ action: "visible"|"hidden"|"deleted", reason? }GET /api/admin/bansPOST /api/admin/bansDELETE /api/admin/bans?id=...GET /api/admin/digestPOST /api/admin/maintenanceGET /api/admin/leadsPOST /api/admin/leads/:id
Admin UI:
/admin/moderation//admin/leads/
docs/visual-personality-plan.md