CV Hub
Resume as Code. Treat your professional identity as a reproducible, versioned system.

Overview
CV Hub is not a portfolio template. It is a system. The project redefines a resume as an engineering artifact — versioned, reproducible, and deployable. Instead of maintaining fragmented representations across LinkedIn, PDF, Notion, and portfolio sites, CV Hub introduces a single declarative source of truth — a YAML file — that drives the entire output pipeline. The result is a fully automated system where one change propagates consistently across every format and every role-specific variation. The very page you are reading is part of it: this case study is rendered from a block-based YAML document by the same engine that builds the CV.
Problem
Traditional resumes are fundamentally broken from a systems perspective. They are: - duplicated across platforms - manually synchronized - context-dependent (DevOps vs Backend vs GameDev) - not version-controlled - not reproducible This leads to drift, inconsistency, and significant maintenance overhead. From an engineering standpoint, this is equivalent to running multiple unsynchronized environments without a single source of truth.
Solution
CV Hub applies infrastructure principles to personal data. The core idea is simple: define once → generate everything → deploy automatically. A single YAML file becomes: - a live website - PDF, DOCX, and TXT artifacts - multiple role-specific CV versions - a versioned, Git-controlled professional profile The system eliminates duplication and enforces consistency by design.
Multi-profile system
Base + delta architecture
Role-specific resumes are generated using a two-layer model: a base CV plus per-profile delta overrides. This allows precise control over positioning without duplicating data.

How profiles work
Each profile is declared once in profiles.yml with an id, a URL slug, and a spec key. At build time merge.mjs walks every profile × language combination and produces a normalized artifact per pair.
The merge is deliberately explicit rather than magical:
- Scalar and list fields in a delta replace the base value outright.
- Experience entries are matched by company name — a delta entry carrying just a company inherits the base entry and overrides only the fields it specifies.
- A profile's experience list fully defines its own ordered set, so each role can curate exactly the history that is relevant to it.
The output is deterministic: the same source always yields the same artifacts, and a build-time guard enforces that a profile's routing slug and its file-spec key stay in sync, so a profile's page can never silently desync from its CV and downloads.
Internationalization
Languages are a first-class axis, not an afterthought. The system renders an N profiles × N languages matrix from a single content base.
- A languages.yml registry defines available languages and the default; the header language switcher is generated from it.
- Every UI string lives in translations.yaml and is resolved through a small makeT() helper with a fallback chain: requested language → English → the key itself, so a missing translation degrades gracefully instead of breaking the page.
- Routing is fully static. [...slug].astro emits one page per profile × language combination, and the default-language home stays at the clean root path.
Chinese translations already ship in the content layer, ready to be switched on by adding a single line to the language registry.
What I built
- Static-first architecture with zero client-side JavaScript by default
- YAML-based Single Source of Truth for all resume data
- Multi-profile system (DevOps, GameDev, etc.) via a deterministic merge pipeline
- Multi-language support with a graceful fallback chain
- Automated document generation (PDF via Playwright, DOCX/TXT via docx.js)
- A block-based case-study engine powering these Deep Dive pages
- Four interchangeable animated background components (CSS and Canvas)
- URL-based theme switching with shareable state
- Fully automated CI/CD pipeline using GitHub Actions with Telegram alerts
- Dynamic environment-aware configuration (siteUrl from GITHUB_REPOSITORY)
- ATS-safe single-column PDF alongside the polished two-column one, from the same data
- One-command onboarding (
npm run init) that wipes the example data and starts a fork clean - Auto-generated, per-language social preview cards from real CV data — no design tool
schema.org/PersonJSON-LD,hreflang, and per-page SEO metadata instead of one generic string site-wide- Print-ready —
Ctrl+Pproduces a clean document, not a dark-theme screenshot - Custom domain support (
SITE_URL/BASE_PATH) on top of the default GitHub Pages URL
Architecture
The system is built as a deterministic pipeline. Data layer: - YAML files (base CV + profile deltas, showcase projects, case studies, translations, changelog) Processing layer: - merge.mjs → produces normalized per-profile, per-language artifacts - document generators → create PDF / DOCX / TXT from those artifacts Presentation layer: - Astro static build → pre-rendered HTML, content validated by typed schemas Deployment: - GitHub Actions → immutable build → GitHub Pages No runtime server. No hidden state. No database. Every output is reproducible from source, and content shape is validated by Zod schemas before a single page is built.
Dynamic backgrounds
The atmosphere of the site is driven by a small library of interchangeable background components. Only one is active at a time — they are designed as a drop-in palette, each self-contained and configured through the same design-token system as the rest of the styling. There are two families: a CSS-only layer that costs almost nothing, and a set of Canvas renderers inspired by the PlayStation XMB interface. Each is shown below.
AnimatedBackground — the default
Pure CSS · zero JavaScript
Four large radial-gradient orbs drift behind the content with a fine noise overlay on top. It is entirely CSS — no JavaScript, no canvas, no main-thread work.
It is also the most performance-tuned: the keyframes animate translation only (no scale), so the browser caches each blurred orb as a texture and simply moves it instead of re-rasterizing an 60px blur every frame. The whole layer is disabled below 768px and honors prefers-reduced-motion.

GalaxyBackground
Canvas · requestAnimationFrame
A canvas-rendered starfield with slow parallax drift, accent-tinted to the active theme. A heavier, more cinematic option for when atmosphere matters more than idle cost.

PlayStationWaves
Canvas · XMB-inspired filled waves
Filled sine waves layered like the PlayStation XMB menu, with a time-of-day hue shift and a non-deterministic start position (seeded from the timestamp) so no two loads look identical. Roughly 18 props expose wave count, speed, amplitude, color and draw quality.

WaveLines
Canvas · XMB-inspired glow lines
A stroke-based cousin of PlayStationWaves: glowing lines arranged symmetrically around a center axis. Each line's glow is composited on its own offscreen canvas to avoid a subtle bug where a shared destination-in mask was eating the background alpha at the edges.

Background performance engineering
Beautiful backgrounds are easy to make janky, so they are treated as a performance surface in their own right.
- The CSS orbs avoid per-frame blur re-rasterization by animating transforms only.
- The full-page gradient lives on a fixed pseudo-element layer instead of background-attachment: fixed, so it never repaints on scroll.
- A fullscreen mix-blend-mode pass was removed once it proved invisible at its opacity but expensive every frame.
- Every animated background is disabled on small screens and respects reduced-motion.
The net effect: rich motion on capable desktops, and a calm, cheap render everywhere else.
Themes
Theming is a set of CSS custom properties layered over a token base. A theme is selected with a ?theme= URL parameter and applied through a tiny inline script, which keeps the state shareable — send a link and the recipient sees exactly the palette you chose.

Document export pipeline
The same merged YAML that renders the site also generates downloadable documents, so the web and offline versions can never drift apart.
- PDF is produced by rendering a print-tuned layout in headless Chromium via Playwright — pixel-faithful to the site.
- DOCX is built programmatically with docx.js, producing a real editable Word document rather than a screenshot.
- TXT is emitted as a clean plain-text fallback for ATS systems and copy-paste.
Every profile × language pair gets its own set of files, generated in CI and linked directly from the matching page.
Prefer Ctrl+P? The live page prints clean too — a dedicated print stylesheet strips the chrome (nav, footer, animated background) and remaps every color token for paper, no separate template to maintain.
Right file, right audience
PDF and DOCX are what a visitor recognizes — the polished resume, and the editable version recruiting agencies reformat with. The ATS-safe single-column PDF and the plain-text export are a different audience entirely: mostly useful to the CV's own owner, pasting into a job-portal upload form or a "paste your resume" textarea — not something a recruiter browsing the site would ever click, and "ATS" reads as jargon rather than a benefit to them.
Which formats show, and in what order, is a site.yml setting rather than a hardcoded button row — a personal site keeps just the two a visitor recognizes, a demo deployment can opt into all four to show the ATS variant off. PDF stays the one visually solid button regardless; everything else is secondary.
Social preview cards
Sharing a link should look like something. Every build screenshots a real, live-rendered page through a headless browser — no manual design tool, no static template — and composites it into a framed, wallpapered 1200×630 card using the active theme's own design tokens, so the card is always in sync with however the site happens to be themed.
One card per language, built from the actual default-profile CV (not a placeholder persona) — every page, including profile and case-study pages, picks the card matching its own language automatically through the same og:image fallback.
Discoverability
The part that's invisible until you check dev tools: a per-page meta description and title instead of one generic string across the whole site, schema.org/Person JSON-LD on CV pages so a name search has something to surface, hreflang alternates wired straight into the same data that drives the language switcher, a canonical URL on every route with no stray double slashes, and a sitemap that stays in sync with what's actually deployed.
CI/CD & automation
Shipping is a single git push.
- GitHub Actions runs the full pipeline on every push to main: merge → generate documents → render PDFs → static build → deploy to Pages.
- The Playwright Chromium binary is cached by lockfile hash, shaving roughly two minutes off a cold deploy.
- A Telegram bot reports the result — success, build failure, or deploy failure — with branch and short commit SHA, degrading silently if no secrets are configured.
- siteUrl and base path are derived from GITHUB_REPOSITORY, so a fork builds and deploys correctly with zero configuration.
- A sitemap covering every profile × language route is generated at build time, with a matching robots.txt.
Performance engineering
Performance is measured, not assumed. - Static, pre-rendered HTML with no hydration cost by default. - The first Showcase cover with a real image is loaded eagerly with high fetch priority; everything else stays lazy — a deliberate LCP optimization. - Scroll-time compositing was profiled and the expensive layers (fixed gradient repaints, a fullscreen blend pass) were removed. - Result: Lighthouse 100 Performance / 100 Accessibility / 100 SEO, with motion that stays smooth on low-end hardware.
The Showcase system
The portfolio half of the site is the same idea applied recursively. Projects are declared in a YAML list, and each can carry a full Deep Dive case study — like this one — assembled from a small set of content blocks: text, image, video, code, divider.
The blocks are validated by a typed schema and rendered by dedicated components, which means a long-form, editorial case study is authored entirely in data, with no bespoke page code. It behaves like a controlled, static alternative to a Notion page: portable, versioned, and fast.
video embeds a YouTube link directly as a responsive player — no self-hosted file required, which came in handy for testing:

Engineering Decisions
- Astro over SPA frameworks to eliminate unnecessary runtime and hydration cost
- Static generation to guarantee performance and predictability
- YAML over a CMS to retain full control and Git-native versioning
- URL-driven state instead of localStorage for shareability and transparency
- Explicit delta files instead of implicit overrides to keep changes observable
- Typed content schemas so malformed data fails the build instead of the browser
DevOps Approach
The project treats a resume as a build artifact. Key principles: - Git-based versioning - Immutable builds - CI/CD automation - Environment-independent deployment - Zero runtime dependencies Every commit produces a fully consistent, deployable state of the system.
Trade-offs
- No CMS — intentional trade-off for control and transparency
- No backend — static-only architecture
- Manual delta creation for profiles — explicit over implicit complexity
- Canvas backgrounds cost more than the CSS default — hence one default, the rest opt-in
- Limited runtime flexibility in favor of determinism
Result
- One edit regenerates all formats across all profiles and languages
- Fully static deployment with zero infrastructure cost
- Fork-ready system with zero configuration required
- Lighthouse: 100 Performance / 100 Accessibility / 100 SEO
- Predictable, reproducible output across environments
Takeaways
CV Hub demonstrates that even non-traditional domains like resumes benefit from engineering rigor. By applying system design principles — single source of truth, deterministic pipelines, and automation — the project transforms a static document into a scalable, maintainable system. This approach generalizes beyond resumes to any domain where data consistency, reproducibility, and multi-target output matter.
Want your own?
Everything on this page — this case study, the CV behind it, even the Rick Roll a few sections up — is a live CV Hub instance. If you have five minutes and know how to use git, you can have your own.
1. Fork it
Fork the repository on GitHub, then clone your fork.
git clone https://github.com/YOUR_ACCOUNT/cv_hub.git
cd cv_hub
2. Start fresh
npm install, then npm run init — wipes the example CV, showcase and profile data this repo ships with and reseeds it from a blank placeholder, so you're editing your own thing instead of someone else's resume. Backs everything up first; npm run init -- --dry-run previews without touching anything.
npm install
npm run init
3. Fill in your data
Everything lives in one file: src/content/cv/en.yaml. Three ways to fill it in — pick whichever is easiest:
Edit the YAML directly— name, summary, experience, skills. Plain text, no build step needed to see the shape of it.Import a JSON Resume— already have one?npm run resume:importconverts it.Feed it to an AI— paste your existing resume (PDF or plain text) into Claude or ChatGPT together with the prompt fromdocs/LLM-CONTEXT.md. It hands back ready-to-use YAML — genuinely the fastest path if you'd rather not touch the schema by hand.
4. Look at it
Open http://localhost:4321 — every save shows up instantly.
npm run dev
5. Ship it
Turn on GitHub Pages once — Settings → Pages → Source: GitHub Actions — then just push. Every commit to main rebuilds and redeploys automatically. No separate deploy step, ever again.
git add .
git commit -m "my resume"
git push