Files
hoelee 060b0f1733
Deploy / build (push) Successful in 19s
docs: record robots.txt encoding rule and sitemap derivation in seo-reference
The crawler/sitemap mechanics now live in the repo doc, not just in the
commit log: robots.txt is a build-time endpoint that must stay pure ASCII
(no charset on a text/plain response means non-ASCII renders as mojibake),
the policy is allow-all with enforcement deliberately left to Cloudflare,
why writing a real robots.txt demotes Cloudflare's placeholder from
replacement to prepend, and that lastmod/hreflang are derived so they must
never be hand-authored.
2026-09-19 22:14:40 +08:00

73 lines
4.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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)
### Crawler / sitemap mechanics (as built)
- `robots.txt` is a **build-time endpoint** (`src/pages/robots.txt.ts`), not a static file in `public/`. It interpolates `SITE.url` so the `Sitemap:` line can't drift. **Keep it pure ASCII** — the response has no charset, so a non-ASCII byte renders as mojibake (`鈥`) in viewers that fall back to a legacy codepage. Check: `LC_ALL=C grep -c '[^ -~]' dist/robots.txt` must be `0`.
- Policy is **allow-all**, including AI *training* crawlers. Rationale: `robots.txt` is advisory and unenforced (Cloudflare documents it as voluntarily honoured), so a blocklist there buys nothing; enforcement, if ever wanted, belongs in Cloudflare AI Crawl Control. `/pagefind/` is the only exclusion (build artifacts, not content).
- **Why a real robots.txt matters at all:** writing one turned Cloudflare's *Content Signals Policy* placeholder from a **replacement** into a **prepend**. On a Free-plan zone with no origin `robots.txt`, Cloudflare serves that placeholder *in place of* your file — a comment block with no `User-agent`, no directives and no `Sitemap:` line. Confirm the origin is serving yours before blaming Astro.
- Sitemap `lastmod` + `xhtml:link` hreflang alternates are **derived**, never authored — see the rule in the project skill. `sitemap-index.xml` legitimately contains exactly one entry (`sitemap-0.xml`) and looks empty in a viewer; the URLs are in `sitemap-0.xml` (77 of them at 29 posts). Don't "fix" the index.
- hreflang exists in **two places on purpose**: in-page `<link rel="alternate">` (from `BaseLayout`'s `altLocaleUrl`) and sitemap `xhtml:link`. Google treats the sitemap as more authoritative when they disagree, so both must be kept in sync — which is automatic for posts and for any page that passes `altLocaleUrl`.
---
## 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.