$my-yaml-resume
A single-file YAML resume that renders into a clean, filterable web page. Recruiters can click a skill and instantly see what you know, how long you've used it, where, and when.
The standout feature is the skill filter: click any tag and the resume dims non-matching entries while surfacing per-skill stats (years used · projects · year range).
01 Features
02 Project structure
The codebase is small and deliberately flat. Most edits happen in public/ and src/.
03 Requirements
- Node.js ≥ 20.x (anything that runs Vite 8)
- npm, pnpm, or yarn (examples use npm)
- git: required for the bundled publishing script
- A text editor that doesn't fight YAML's whitespace
04 Install & run
From a fresh clone of the repository:
# 1. install deps $ npm install # 2. create your env file $ cp .env.example .env # 3. point it at your resume (or use the example) $ cp public/resume.example.yaml public/resume.yaml # 4. start the dev server $ npm run dev ➜ Local: http://localhost:5173/
Then open http://localhost:5173/; edits to public/resume.yaml hot-reload.
05 The YAML file
Everything you see on the page comes from one file. The full schema lives in
src/types.ts; the short version is below.
description: "Software engineer focused on…" name: "Kostya Arminleg" title: "Software Engineer" location: "Example City, USA" links: github: "https://github.com/…" linkedin: "https://linkedin.com/in/…" # Pretty-name your skill slugs. Slugs are what you tag with; # titles are what's rendered. skillkey: react: "React" typescript: "TypeScript" postgresql: "PostgreSQL" experience: - name: "Example Co." title: "Senior Engineer" start: "2021/07/12" end: "2025/02/28" skills: [agile, linear] responsibilities: - description: "Led the dashboard rewrite." skills: [react, typescript, vite]
Same shape for projects and education. Any field marked
hidden: true is skipped at render time, so you can keep older roles
in the file without showing them.
| Field | Type | Notes |
|---|---|---|
name | string | Job / school / project title |
start / end | YYYY/MM/DD | Drives the "years used" math |
skills | string[] | References slugs from skillkey |
responsibilities | object[] | Each one carries its own skills |
hidden | boolean | Hide without deleting |
06 Environment vars
Copy .env.example to .env and adjust. The Vite-prefixed vars get baked into the build; the others are read by the publish script.
VITE_RESUME_FILE=/resume.yaml # the YAML to load at runtime VITE_DEFAULT_TITLE="My Resume" VITE_DATE_FORMAT_MONTH_YEAR="MMM YYYY" VITE_DATE_FORMAT_YEAR="YYYY" VITE_ICON_SIZE=32 # publish.mjs reads these RESUME_BUILD_DIR="dist" RESUME_GIT_PAGES_BRANCH="pages" RESUME_CUSTOM_DOMAINS="resume.example.com"
07 Customize
- Colors & typography: open
src/style.css. Tokens are at the top. - Layout: sections live under
src/components/(one file per section). - Skill-stat math:
src/hooks/useSkillStats.tsderives years and project counts from your YAML; tweak the heuristic there. - Theme provider:
src/contexts/ThemeContext.tsxhandles dark/light + the boxed window chrome.
Tip: the only file you must edit is public/resume.yaml. Everything else has sensible defaults.
08 Publishing
The repo ships a one-command publish script that builds the site and pushes it to a separate pages branch. It works out of the box with Codeberg Pages or GitHub Pages.
$ npm run publish
The script (scripts/publish.mjs) does, in order:
- Clean any prior worktree and the build branch
- Create a fresh worktree on an orphan
pagesbranch - Run
vite buildinto a temp dir - Empty the worktree (preserving
.git) and copy the build in - Drop a
.domainsfile if you've setRESUME_CUSTOM_DOMAINS - Commit and force-push to
origin/pages - Tear down the worktree and temp dir
Heads up: the publish script force-pushes the pages branch and clears $RESUME_BUILD_DIR before building. Don't keep anything you care about in there.
09 npm scripts
| Command | What it does |
|---|---|
npm run dev | Vite dev server with hot reload |
npm run build | Type-check (tsc -b) then build for production |
npm run preview | Serve the production build locally |
npm run lint | ESLint over the whole tree |
npm run publish | Build & deploy to the pages branch |
10 The skill filter
Three React contexts cooperate to make filtering feel instantaneous:
SkillKeyContext: owns the slug → pretty-name map and agetSkillTitle()helper.SkillFilterContext: owns the active filter set +toggleSkillFilter()andisActive(skills)predicates.useSkillStats: given a slug, returns{ years, projects, firstUsed, lastUsed }by walking the parsed YAML.
Every renderable block calls isActive(theseSkills) and dims itself if no filter matches. The
<Filterable> wrapper is the standard primitive; see src/components/Filterable.tsx.
// inside a section/responsibility const { isActive } = useSkillFilter(); return ( <div className={isActive(skills) ? '' : 'dimmed'}> … </div> );
11 License
Released under the GNU General Public License v3.0 or later. You can use, modify,
and redistribute it; derivative works must remain under GPL-3.0+. Full text is in
LICENSE.
© 2026 Sara McCutcheon <code@saram.cc>. This program is distributed without warranty; see gnu.org/licenses for details.