← back to blog

My resume is code now

#tech·typst·ci-cd·resume

For years my resume lived in a Word doc. Every edit was a small fight. Add one bullet and the whole thing reflows, a job slides onto a second page, the gap between two sections quietly stops matching the others. I was spending more time nudging margins than writing.

So I did the obvious DevOps thing and turned it into code.

## Meet Typst

Typst is a typesetting system, a younger and friendlier take on what LaTeX does. You write plain text, it gives you a clean, precise PDF. The same typeset quality, without the LaTeX pain.

For a resume that is the whole point. The words are just text I can edit anywhere, and the layout lives in code I control instead of a word processor guessing what I meant. It compiles in well under a second, so previewing a change feels instant.

## How a change ships

Because the resume is just a text file in git, every change runs through the same path my code does.

### One file, with history

I can diff it. I can look back and see exactly what changed between the version I sent in March and the one I sent last week. No more resume_final_v3.docx.

The layout is codified too. A few spacing constants and one entry() helper keep every job block consistent, so I never hand-space anything:

// one renderer for every org/role block, so spacing is identical everywhere
#let entry(org, loc, role, first: false) = ...

#entry("Acme Corp", "San Francisco, CA", "Senior Software Engineer")

Each meaningful edit bumps a version, and that version prints in the PDF footer, so I always know which build a recruiter is actually looking at.

### On every push

A push to main that touches the resume wakes up GitHub Actions. The workflow only runs for the file that matters:

on:
  push:
    paths: ['resume/resume.typ']

From there it does three things: bump a version tag (a patch by default, or #minor / #major from the commit message), compile the PDF with Typst, and publish it.

typst compile resume/resume.typ cv.pdf --input version=$TAG

Resume publish pipeline

The published PDF lands in two places on Cloudflare R2: the latest at a fixed URL, and a versioned archive beside it so old versions never disappear. My site links the latest one.

I pulled the whole thing into a small template if you want to try it: resume-as-code. Clone it, drop in your own content, push, and you get a built, versioned PDF on every change. It publishes to GitHub Releases, so it runs with zero secrets.

## Making it ATS-friendly

A resume usually meets an applicant tracking system before it meets a person. The ATS reads your PDF by pulling the text back out of it, so what matters is whether that text comes out clean and in the right order.

Here is the part worth knowing: a PDF can look perfect and still extract badly. That is a property of the layout, not the tool. Multi-column designs, text tucked into tables, or fancy letter-spacing can all scramble what the parser sees.

So I keep one habit. Open the PDF, select all, copy, and paste into a plain text editor. What you get back is roughly what the ATS gets.

The first time I ran that test, a letter-spaced title came out like this:

S O F T W A R E   E N G I N E E R

Spaced-out glyphs the parser can misread as separate words. Dropping the tracking brought it back clean:

SOFTWARE ENGINEER

The fixes are boring on purpose: single column, real selectable text, standard headings. Typst keeps that easy, and the copy-paste test catches the rest before it reaches a recruiter.

## Where this goes next

Versioning was never really the point. The point is what codifying unlocks.

If the resume is structured content plus a layout, I can build automation on top of it. Given a job description, it could surface the experience I already have that best fits the role, phrased in their words. Tailored, not spammed across fifty applications.

There is one hard rule:

It can reorder, emphasize, and reword. It cannot invent.

No skill I do not have, no achievement I did not earn. A resume that lies falls apart in the interview anyway. I want to show the real me in the best light for each role, not become someone I am not on paper.

That part is still ahead of me. But it only became possible once the resume stopped being a document I fight and started being something I can build.