Multilingual (EN/DE) static CV published with GitHub Pages. Content lives in modular YAML data files, rendered to HTML via Nunjucks templates and styled with Sass. Bilingual PDFs are generated deterministically from the built HTML with Playwright (headless Chromium).
- English at
/, German at/de/ - Brief English at
/brief/, brief German at/de/brief/ - GitHub Pages project base-path support (
/cv) - Theme support, full and brief variants
- Build-time data validation and HTML validation
- Local Font Awesome icons copied from a pinned npm dependency; no analytics, no Hugo
- Node — the version pinned in
.nvmrc(managed vianvm) - Chromium for Playwright — installed once via
npx playwright install chromium(only needed for PDF generation)
nvm install
nvm use
npm install
npx playwright install chromium # for PDF generationmake install does all of this (npm dependencies and the Chromium browser).
make serve # build the web output and start a local serverOpen the printed URL. To regenerate the PDFs locally, run make pdf.
For iterative styling, run make watch (or npm run watch:css).
data/
en/ English content, one file per section
metadata.yaml title, abstract, section labels, icons, lang switcher, footer
education.yaml
experience.yaml
skills.yaml
interests.yaml
references.yaml
de/ German content, mirrors data/en/
templates/ Nunjucks templates and partials
src/
data.mjs Loads + merges the per-section YAML, validates it
build.mjs YAML + Nunjucks → dist/ (validates before rendering)
generate-pdf.mjs Serves dist/ and renders the EN/DE PDFs with Playwright
validate-data.mjs Standalone data validation CLI
styles/ Sass source
thesis.pdf Static source asset (linked from the CV)
dist/ Generated output (gitignored)
Each language is assembled from the section files under data/<lang>/. The
build merges them in memory before rendering, so editing a single section (e.g.
data/en/experience.yaml) does not touch the rest. Education and experience
entries carry a stable id, which keeps the two languages aligned and gives
each entry a DOM id (#education-<id>, #experience-<id>).
Validation runs automatically inside npm run build and can also be run on its
own:
npm run validate:dataIt checks: required fields; supported languages; matching entry IDs and ordering
between English and German; matching variants declarations; matching skill
category/item IDs; valid theme names; valid and unique PDF filenames. Errors
name the offending language, file, and field, e.g.:
[de] data/de/experience.yaml: "experience" ids/order [...] do not match [en] [...]
Every education/experience entry has a variants array. The full variant
renders the complete CV; brief filters to entries tagged brief and uses
purpose-written brief_summary / brief_points fields where present. Skills
are rendered in both variants from categorized data.
The default build publishes both variants side by side:
npm run buildOutput routes:
- Full:
/and/de/ - Brief:
/brief/and/de/brief/
Rendered pages include a same-language switch between full and brief variants, plus the existing language switcher.
For a targeted local rebuild of only one variant:
npm run build:html -- --variant briefIf a matching template (brief.njk) exists it is used; otherwise full.njk is
rendered with the filtered data. The brief template intentionally omits
interests and references to keep the PDF to one A4 page.
Themes are CSS custom-property blocks generated from the Sass $themes map
(classic, clean). Switch the active theme via the theme field in
data/<lang>/metadata.yaml.
By default, asset and link URLs are root-relative (e.g. /assets/styles.css).
For GitHub project Pages served under a subpath like /cv/, pass --base to
both the build and the PDF generator:
npm run build:html -- --base /cv
npm run pdf -- --base /cvOr set the BASE_PATH environment variable. The GitHub Actions workflow sets
this automatically from the Pages configuration.
npm run build # compile CSS + generate full + brief HTML → dist/ (web build)
npm run build:css # CSS only
npm run build:html # HTML only, both variants unless --variant is passed
npm run pdf # render full + brief EN/DE PDFs from dist/ (requires Chromium)
npm run build:prod # complete production build: web build + PDFsThe same steps are available through make (each target uses nvm):
make install # npm install + Chromium
make build # web build → dist/
make build-brief # targeted brief HTML build → dist/brief/ and dist/de/brief/
make pdf # web build + full + brief PDFs → dist/
make build-prod # complete production build
make watch # rebuild CSS on change
make serve # build + start local server
make check # fast: web build + validate HTML
make check-prod # full: data validation + build + validate HTML + PDFssrc/generate-pdf.mjs renders all full and brief PDFs from the already-built
HTML — it does not rebuild the site. It:
- serves
dist/over a local HTTP server (honouring the base path), - loads
/(English) and/de/(German), - waits for
document.fonts.ready, - verifies the expected section structure, that the stylesheet applied, and that the Font Awesome icon font loaded, failing clearly if a page, style, font or same-origin asset is missing,
- writes tagged PDFs with outlines and print backgrounds, using CSS
@pagesizing via Playwright'spreferCSSPageSize, - fails if a brief PDF is not exactly one A4 page,
- always closes the browser and server, including on error.
The full output filenames match the existing public links
(lebenslauf_till_breuer.pdf, lebenslauf_till_breuer_de.pdf). Brief PDFs are
written as lebenslauf_till_breuer_brief.pdf and
lebenslauf_till_breuer_kurz.pdf. All PDFs are written into dist/. Full print
styling targets a compact, two-column, ~3-page A4 layout; brief print styling
targets one A4 page. Individual entries are kept from splitting across pages.
GitHub Pages is built by Actions (.github/workflows/pages.yml).
On every push to master the workflow installs dependencies (npm cached) and
Chromium, validates the data, builds CSS + HTML with the correct Pages base path,
validates the HTML, generates both PDFs, and deploys dist/ as a Pages artifact.
Typical flow:
make build-prod
git add -A && git commit -m "..."
make deploy # runs make check-prod (full artifact), then pushes to originOne-time setup: in Settings → Pages, set Source to GitHub Actions.
- Generated (gitignored): everything under
dist/, including the full and brief CV PDFs, copied Font Awesome fonts, andthesis.pdf. The CV PDFs are not checked in — they are produced from the HTML on every build. - Tracked sources:
data/,templates/,src/,package-lock.json, andthesis.pdf(a static source asset linked from the CV). Font Awesome is a pinned npm dependency and its webfont files are copied intodist/during the build.