- Extract CSS into src/styles/global.css with design tokens (hoelee.com brand palette) - Self-hosted Inter + JetBrains Mono via @fontsource - Sticky nav + manual light/dark toggle + copy-button code blocks - Author card + Person JSON-LD (E-E-A-T), article meta, related posts, reading time - Full SEO: canonical, OG (with dims), twitter:card, favicon (all sizes), RSS + sitemap - New /categories/ and /categories/[category]/ pages - Add docs/ knowledge guides (content, design, seo, ops) + README index - Brand name: hoelee.dev -> Mr Hoelee
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
# Content Guide — what to write & what to avoid
|
||||
|
||||
Read this **before** planning or writing any post on blog.hoelee.com. It encodes the positioning, taxonomy, post types, cadence, and anti-patterns so every piece of content serves the two goals: get hired, and win freelance clients.
|
||||
|
||||
---
|
||||
|
||||
## 1. The job of every post
|
||||
|
||||
A post is not "content marketing". It is a **proof-of-expertise artifact**. After reading it, a recruiter or client should think: *"this person can actually do this, and I trust how they explain it."*
|
||||
|
||||
Two hard rules flow from that:
|
||||
1. **Attach your identity to every post** — photo, bio, links. Anonymous content kills E-E-A-T and defeats the whole purpose.
|
||||
2. **Pitch to learners, not experts.** Most search traffic comes from people learning. Write down, not up.
|
||||
|
||||
---
|
||||
|
||||
## 2. Categories (stable — don't invent new ones casually)
|
||||
|
||||
Use these 7 categories. Everything else is a free-form **tag** (`traefik`, `solidity`, `gitea`, `n8n`…).
|
||||
|
||||
| Category | What goes in it |
|
||||
|---|---|
|
||||
| `engineering` | Java/Spring, PHP/CodeIgniter, React/TS, WordPress deep dives |
|
||||
| `devops` | Docker/Portainer, Traefik, Cloudflare tunnels, NAS, backups, monitoring — **my most differentiated material** |
|
||||
| `ai` | n8n workflows, Telegram bots, local LLM (LM Studio), mem0 memory stack, TTS |
|
||||
| `web3` | Solidity, Foundry/Hardhat, ERC-20/721, Chainlink — honestly framed |
|
||||
| `tutorials` | Beginner-facing how-tos |
|
||||
| `case-studies` | "How I built X" — the portfolio |
|
||||
| `notes` | Short low-friction entries: fixes, gotchas, link roundups |
|
||||
|
||||
---
|
||||
|
||||
## 3. Post types that work (ranked by hiring ROI)
|
||||
|
||||
1. **"How I built X" case studies** — highest value. Recruiters read these. (DigiKedai bot, self-hosted mem0, mailcow email platform, NAS product delivery, SifuMail.)
|
||||
2. **"Hard problem → solution" tutorials** — e.g. "The Traefik forward-auth gotcha that cost me a day", "Site-to-site OpenVPN behind CGNAT".
|
||||
3. **Gotcha / debugging posts** — short, extremely Google-friendly, compound over time.
|
||||
4. **Tool roundups & comparisons** — I have genuine first-hand material (Astro vs WP, Gitea vs GitHub, Foundry vs Hardhat).
|
||||
5. **Config / recipe posts** — reusable Traefik/Docker/Pi-hole configs with explanation.
|
||||
|
||||
---
|
||||
|
||||
## 4. The "hard job done → public post" template
|
||||
|
||||
When I finish a difficult piece of work, publish with this shape. It is simultaneously a tutorial, a case study, and a proof-of-expertise — the highest-ROI post type I can write.
|
||||
|
||||
```
|
||||
① The problem → phrased as the searchable question a learner would type
|
||||
② What I tried & why it failed → the debugging story (this is what no one else can copy)
|
||||
③ The fix → runnable code/config, explained
|
||||
④ What I'd do differently → shows judgment, not just luck
|
||||
⑤ The result → one quantified outcome
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Cadence
|
||||
|
||||
- **Realistic target:** 2 posts/month to start, then 1/week.
|
||||
- Sonmez playbook: Month 1–2 = 1/week → Month 3 = 2/week → Month 4+ = 1–2/week mixing 500-word tactical posts with 2,000+ word guides.
|
||||
- **Ship a `notes` entry anytime** I solve something — keeps the feed alive between deep posts.
|
||||
- Long guides bring search traffic; short posts keep me consistent.
|
||||
|
||||
---
|
||||
|
||||
## 6. Frontmatter shape (reference)
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: "How I built the DigiKedai Telegram bot"
|
||||
description: "A ~155-char meta description with the target keyword."
|
||||
pubDate: 2026-09-06
|
||||
category: case-studies
|
||||
tags: ["telegram", "n8n", "docker", "cloudflare"]
|
||||
ogImage: "/og/digikedai-bot.png" # 1200×630, custom per post
|
||||
draft: false
|
||||
lang: en
|
||||
---
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. What to AVOID (anti-patterns)
|
||||
|
||||
| Anti-pattern | Why it hurts |
|
||||
|---|---|
|
||||
| **Anonymous / no author identity** | Kills E-E-A-T; defeats the name-search strategy. Always show photo + bio + sameAs links. |
|
||||
| **Writing for experts, not learners** | Most search traffic is beginners. |
|
||||
| **AI-spam / content-farm filler** | Google aggressively demotes unedited AI churn. My first-hand case studies are the moat — never dilute them with filler. |
|
||||
| **Perfectionism before publishing** | Procrastination. Ship it, edit later. A dormant blog is worse than an imperfect one (mine was dormant Dec 2023 → now). |
|
||||
| **Reused logo as og:image** | Every post needs its own 1200×630 image, or shares look broken. |
|
||||
| **Orphaned / thin pages** | hoelee.com has ~400 orphaned image-attachment pages. Don't recreate — keep the sitemap clean. |
|
||||
| **Hosting on Medium/Dev.to as primary** | Platform risk + no SEO ownership. Use them for **syndication only** (cross-post + canonical back to my domain). |
|
||||
| **Overclaiming Web3 experience** | Be honest: "learning Foundry, deployed test ERC-20/721" is credible; "production DeFi engineer" is not yet. |
|
||||
| **Broken resume/CV link** | About page + author bio must link a *working* resume. Fix the source of truth. |
|
||||
| **Monetizing before audience** | No ads, sponsored posts, or ebooks yet. Build trust + traffic for a year first. |
|
||||
|
||||
---
|
||||
|
||||
## 8. i18n policy
|
||||
|
||||
- **English is primary and non-negotiable** — the whole SEO strategy targets English queries.
|
||||
- **Chinese (zh):** translate only the 2–3 best case studies. Cheap differentiation, opens zh-SG/zh-MY search.
|
||||
- **Malay: skip for v1** — no dev-audience demand (the main site already has a dangling Malay config; don't repeat it).
|
||||
- Never let "I should translate this" block publishing an English post.
|
||||
@@ -0,0 +1,72 @@
|
||||
# Design Guide — what the blog UI must have & must avoid
|
||||
|
||||
Read this **before** touching theme, layout, typography, color, or SEO markup. Priority order is fixed: **reading experience → SEO → brand → everything else.**
|
||||
|
||||
---
|
||||
|
||||
## 1. Non-negotiables (reading experience first)
|
||||
|
||||
| Rule | Spec |
|
||||
|---|---|
|
||||
| **Measure (line length)** | 65–75 characters per line. Single biggest readability win. |
|
||||
| **Body typography** | 16–17px, `line-height: 1.6–1.7`, a clean sans (Inter / system-ui / Source Sans). |
|
||||
| **Headings** | A character font (Plus Jakarta Sans / Space Grotesk) or keep Inter for minimalism. |
|
||||
| **Code** | Proper monospace (JetBrains Mono / Fira Code) + distinct background block + **syntax highlighting + copy button + language label**. Non-negotiable for technical posts. |
|
||||
| **Light + dark mode** | Both. Dev audience splits ~50/50. |
|
||||
| **Color** | 2-accent palette (primary + semantic green/red for correct/wrong in tutorials). Avoid pure black / pure white. |
|
||||
|
||||
---
|
||||
|
||||
## 2. Required components
|
||||
|
||||
- **Sticky header** — name/logo + nav (Blog / Categories / About / RSS).
|
||||
- **Article header** — title, pub date, reading time, category + tags, author card (photo, 1–2 line bio, LinkedIn + GitHub + git.hoelee.com links).
|
||||
- **Author box / ProfilePage schema** on every post (see SEO below).
|
||||
- **Related posts** (3 cards) — internal linking + keeps readers on-site.
|
||||
- **RSS link** in footer — how the technical audience follows me.
|
||||
- **Custom og:image per post** — 1200×630, never the reused logo.
|
||||
|
||||
---
|
||||
|
||||
## 3. Responsive & media
|
||||
|
||||
- **Mobile-first**: design 390px, then desktop 1280px.
|
||||
- **Astro Image** for automatic optimization/WebP (Core Web Vitals win).
|
||||
- **Declare `og:image:width/height` explicitly** — mis-declared dimensions get preview cards rejected by WhatsApp/Telegram/LinkedIn.
|
||||
- **Favicon**: reference all sizes (`.ico` + 32×32 + 192×192 PNG + apple-touch 180×180). "Favicon not showing" is usually browser cache, not a missing asset — `curl` the live files before "fixing".
|
||||
|
||||
---
|
||||
|
||||
## 4. Theme / stack decisions (settled)
|
||||
|
||||
- **Astro 5 static** (already scaffolded). Rationale: TypeScript/React skill surface, true static output, low attack surface, first-class i18n routing, and the repo itself is a portfolio artifact.
|
||||
- **Theme base:** AstroPaper-style minimalism (light/dark, fuzzy search, RSS + sitemap defaults). Don't rebuild from scratch.
|
||||
- **Skip for v1:** WordPress-headless (two systems), Next.js SSR/ISR (heavier than needed), self-built blog engine (the classic time sink).
|
||||
|
||||
---
|
||||
|
||||
## 5. SEO checklist (2026 — two lanes)
|
||||
|
||||
Optimize for **both** classic SEO *and* GEO (getting cited by Google AI Overviews / ChatGPT Search / Perplexity).
|
||||
|
||||
- **E-E-A-T first:** real `Person`/`ProfilePage` schema around the author bio, with `sameAs` → LinkedIn, GitHub, git.hoelee.com.
|
||||
- **Per-post meta:** unique title (≤60 chars), description (≤155), canonical, og:image (1200×630) + `twitter:card`.
|
||||
- **Sitemap.xml + RSS + robots.txt** — generated automatically.
|
||||
- **Core Web Vitals:** static site → 90+ LCP/CLS/INP easy. Verify with headless-Chrome screenshot + Lighthouse on 390px **and** 1280px.
|
||||
- **Internal linking:** related-posts module + link pillar posts (case studies) from every tutorial.
|
||||
- **Question-shaped headings:** write H2/H3 as the actual question a learner types. Rewarded by Google *and* AI engines.
|
||||
- **Name consistency:** same name ("Lee Teong Hoe" / "Mr Hoelee") + photo + handles across blog, LinkedIn, GitHub, git.hoelee.com. Fragmented identity is the #1 personal-brand SEO killer.
|
||||
|
||||
---
|
||||
|
||||
## 6. What to AVOID in design
|
||||
|
||||
| Anti-pattern | Why |
|
||||
|---|---|
|
||||
| Heavy JS / client-side rendering | Static output is the point — don't reintroduce runtime cost. |
|
||||
| Reused logo as og:image | Already an issue on hoelee.com. Custom per post. |
|
||||
| Anonymous/absent author card | Kills E-E-A-T. |
|
||||
| Wall-of-text without code blocks | Technical posts need formatted, copyable code. |
|
||||
| No RSS / sitemap | Both are free in Astro; omitting them is pure loss. |
|
||||
| Orphaned pages / thin content | hoelee.com already has ~400; keep this sitemap clean. |
|
||||
| Over-styled "agency look" | This is a personal engineering blog — credibility over flash. |
|
||||
@@ -0,0 +1,65 @@
|
||||
# Ops & Deploy Runbook — blog.hoelee.com
|
||||
|
||||
How this blog is built, deployed, and kept alive. Read when debugging CI/CD, deployment, or hosting.
|
||||
|
||||
---
|
||||
|
||||
## Pipeline
|
||||
|
||||
```
|
||||
Markdown (src/content/posts) → Astro build (dist/) → Gitea Actions → act_runner on unRaid
|
||||
→ nginx container → Cloudflare tunnel → Cloudflare CDN → blog.hoelee.com
|
||||
```
|
||||
|
||||
- **Repo:** `git.hoelee.com/hoelee/hoelee-blog` (push here first, then GitHub — standing convention).
|
||||
- **Runner:** `act_runner` on unRaid (verified working: Gitea 1.27.3 + runner 3.3.2, PAT-secret pipeline).
|
||||
|
||||
---
|
||||
|
||||
## Write & publish a post
|
||||
|
||||
1. Create `src/content/posts/<slug>.md` with frontmatter (see `content-guide.md` §6).
|
||||
2. Push to `main`.
|
||||
3. CI builds `dist/` and deploys to the nginx container automatically.
|
||||
4. Verify live: `curl -I https://blog.hoelee.com/<slug>` → expect 200.
|
||||
|
||||
---
|
||||
|
||||
## Build pitfalls (from prior Astro work — check these first)
|
||||
|
||||
- **No JSX helper components in frontmatter** — inline JSX directly in `.map()` calls, or the build fails with `Expected ">" but found "class"`.
|
||||
- **Import depth** — `src/pages/*.astro` use `../layouts/…`; subdirs use `../../`. Wrong level = `Could not resolve`.
|
||||
- **Footer must render AFTER `<main>`** — a shared header+footer component renders footer above content otherwise.
|
||||
- **`npm install` scripts blocked** can leave esbuild's binary missing → check `npm warn install-scripts` at install time.
|
||||
|
||||
---
|
||||
|
||||
## Verify before shipping a layout change
|
||||
|
||||
Headless-Chrome screenshot + vision check on **both** 390px and 1280px:
|
||||
|
||||
```bash
|
||||
CHROME="/c/Program Files/Google/Chrome/Application/chrome.exe"
|
||||
"$CHROME" --headless --disable-gpu --window-size=1280,2400 \
|
||||
--screenshot="$(cygpath -w $LOCALAPPDATA/Temp/blog.png)" \
|
||||
--virtual-time-budget=4000 "http://localhost:4321/"
|
||||
```
|
||||
|
||||
Overflow diagnosis (real numbers, not screenshot guessing): check `scrollWidth > innerWidth` via CDP `Runtime.evaluate`.
|
||||
|
||||
---
|
||||
|
||||
## Hosting notes
|
||||
|
||||
- Static output = trivial to serve from any nginx/OpenLiteSpeed dir. Cloudflare in front gives CDN + HTTPS + DDoS.
|
||||
- Cloudflare Pages **git integration supports GitHub/GitLab only — not Gitea**. Current setup (Gitea → self-hosted runner → nginx) sidesteps this entirely and is the reason the pipeline is the way it is.
|
||||
- Full self-hosting also avoids Cloudflare Pages' 25MB/file, 20k-files limits — a decision already baked into this architecture.
|
||||
|
||||
---
|
||||
|
||||
## Health checks
|
||||
|
||||
- `curl -I https://blog.hoelee.com/` → 200, correct `content-type`.
|
||||
- `curl https://blog.hoelee.com/sitemap.xml` → lists all published posts.
|
||||
- `curl https://blog.hoelee.com/rss.xml` → non-empty.
|
||||
- DNS: `blog.hoelee.com` resolves through Cloudflare (proxy enabled).
|
||||
@@ -0,0 +1,64 @@
|
||||
# SEO & Metadata Reference — blog.hoelee.com
|
||||
|
||||
Operational reference for the SEO/GEO decisions baked into this project. Read when adding posts, changing `<head>` markup, or debugging search visibility.
|
||||
|
||||
---
|
||||
|
||||
## Two-lane strategy (2026)
|
||||
|
||||
Optimize for **both**:
|
||||
1. **Classic SEO** — Google blue links (still the majority of name searches).
|
||||
2. **GEO (Generative Engine Optimization)** — getting cited by Google AI Overviews, ChatGPT Search, Perplexity. They pull from well-structured, question-answering content.
|
||||
|
||||
---
|
||||
|
||||
## Identity & E-E-A-T (do this first, once)
|
||||
|
||||
The entire blog strategy is a **name-search play**, so identity must be airtight:
|
||||
|
||||
- **One name everywhere:** "Lee Teong Hoe" / "Mr Hoelee" — pick one and keep it identical on blog, LinkedIn, GitHub, git.hoelee.com.
|
||||
- **One photo** across all profiles.
|
||||
- **`sameAs` handles** in the `Person`/`ProfilePage` schema → LinkedIn, GitHub, git.hoelee.com.
|
||||
- **`ProfilePage` schema** wrapping every post's author bio — how Google connects my writing to my professional identity.
|
||||
|
||||
---
|
||||
|
||||
## Per-post checklist
|
||||
|
||||
- [ ] Unique title ≤ 60 chars (target keyword near front)
|
||||
- [ ] Meta description ≤ 155 chars
|
||||
- [ ] Canonical URL set
|
||||
- [ ] Custom `og:image` (1200×630) — **never** the reused logo
|
||||
- [ ] `og:image:width/height` declared (else WhatsApp/Telegram/LinkedIn reject the preview)
|
||||
- [ ] `twitter:card: summary_large_image`
|
||||
- [ ] Category + tags set
|
||||
- [ ] Author card present
|
||||
- [ ] Internal links to ≥1 pillar post
|
||||
|
||||
---
|
||||
|
||||
## Structural checklist (site-wide)
|
||||
|
||||
- [ ] `sitemap.xml` + `robots.txt` + RSS — auto-generated by Astro
|
||||
- [ ] Core Web Vitals 90+ (verify Lighthouse on 390px **and** 1280px)
|
||||
- [ ] Question-shaped H2/H3 (the phrasing a learner actually searches)
|
||||
- [ ] Related-posts module (3 cards) on every post
|
||||
- [ ] No orphaned/thin pages (avoid the hoelee.com ~400-attachment mistake)
|
||||
|
||||
---
|
||||
|
||||
## Syndication policy
|
||||
|
||||
- Primary home = **this domain** (own the SEO).
|
||||
- **Cross-post to Dev.to / Medium** with a `rel=canonical` pointing back to blog.hoelee.com — reach without platform lock-in.
|
||||
- Never make a third-party platform the primary host.
|
||||
|
||||
---
|
||||
|
||||
## The "hard job → post" GEO framing
|
||||
|
||||
When writing, phrase the opening as the exact question someone types into an AI engine or Google:
|
||||
|
||||
> "How do I set up site-to-site OpenVPN behind CGNAT?"
|
||||
|
||||
Then answer it directly with clear H2/H3 headings. This is what both Google and the AI engines cite.
|
||||
Reference in New Issue
Block a user