Files
hoelee-blog/docs/design-guide.md
T
hoelee 0dae8b0daa
Deploy / build (push) Successful in 34s
fix(art): give both 2026-10-20 posts their artwork, and gate the build on it
The YuE2 and subtitles posts shipped text-only: render_from_art.mjs only READS
ogImage/banner, and after the 2026-10-19 bulk migration no script had to write
them, so both pages advertised /og-default.jpg and rendered no hero banner.

- new metaphors (in build_captions.py, frozen here via sync_prompts_catalog.py),
  captions, art-meta evidence, banner plan and the five derived files per slug
- frontmatter: ogImage + banner in EN and ZH for both posts
- scripts/check-assets.mjs is now step 1 of npm run build, so CI fails when a
  published post lacks a language-correct og card, banner, banner .webp or
  400/800 list thumb. It reported 16 problems on the unfixed tree, all here.
2026-10-20 23:37:52 +08:00

6.0 KiB
Raw Blame History

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 (6 photo cards: 2 across on desktop, 1 across on phones with only the first 4 shown) — internal linking + keeps readers on-site. Each card shows the post's own language-specific share card, so an EN page never shows ZH art.
  • 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).

  • Image format policy (measured 2026-10-16 — do not guess this one):

    Asset Served as Why
    og:image / twitter:image PNG (1200×630) Social scrapers (WhatsApp / LinkedIn / FB / Telegram) do not reliably render WebP share cards. Compatibility beats bytes here.
    Article banner in-page WebP q78 + PNG fallback via <picture> 6.02 MB → 2.52 MB across 70 banners (42%); JPEG would be larger (q85 = 129% of PNG) because the art is flat terminal-style with sharp text edges.
    List thumbnails WebP q72 at 400w + 800w (/og/thumb/<name>-400.webp) Rows display at ~398px; they used to fetch the 1200×630 PNG (~45 KB). 400w is 5.8 KB, 800w is 16.1 KB. The homepage went from ~450 KB of thumbnails to ~62 KB.
    Related-post cards The post's own share card (/og/<slug>.jpg, ZH /og/zh-<slug>.jpg), loading="lazy" The card must match the page language, and the thumb WebPs are keyed by base slug — using them would print EN art on ZH pages. 6 cards ≈ 390 KB, deferred below the fold.
    Real photos (author, hardware) WebP q82 + JPEG fallback Continuous-tone images are the opposite case from the flat art above.

    Regenerate all derivatives after adding a post: uv run --with pillow python scripts/optimize-images.py.

  • Every published post must point at its own art. scripts/check-assets.mjs runs as the first step of npm run build (so CI fails, not just warns) and rejects a post whose ogImage or banner is missing or misnamed, or whose banner .webp / -400 / -800 thumbs are absent. The renderers only read frontmatter — nothing writes these two fields for a new post, which is how two 2026-10-20 posts shipped on /og-default.jpg with no hero banner.

  • 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.