I wrote my resume in HTML. Here's what I learned.

2024-08-01 by Gabriel C. Lins

Around once or twice a year I try to update my résumé, even if I'm not actively looking for a job or passing it around. I find that it's good practice to always keep the projects you're proud of collected somewhere. It's the whole reason I have this site!

The last time I tried to update it, that actually ended up being quite the task. I actually quite a few things.

Why?

Not only was my latest CV horrendously out of date (I've been lucky enough to land a job I like), the website I used to build it (shoutout to Reactive Resume!) was down that day (they're free and open source, so no shade there).

At first I decided I would rewrite it the normal way – you know, either use a word processor like a regular living human being or generate a CV off my LinkedIn profile or something. However, none of these were really doing it for me (picky, I know). To add to that sentiment, I've wanted to go back to raw HTML and CSS for a long time now: it's been years since I've brushed up on web tech that required minimal preprocessing and polyfilling. Turns out browsers are pretty good nowadays.

I don't know what makes a good resume

The truth is I have no idea what makes a good resume. Headhunters look for keywords – or more cinically, buzzwords –, managers look for real world experience. I have both, but so does most everybody. So what makes a CV stand out?

If you think I have an answer for you, I'm sorry to disappoint. You can click away now.

In my series of attempts to make a good CV, I got infinitely frustrated. None of them look good! If you try to fit only the important stuff and keep it one page long, it feels shallow. If you try to be too thorough, you can be sure nobody will read it through.

Like I said at the start of this article, there's a key difference this time: I'm not really looking for work, so I can really do whatever. I've also been looking for excuses to sharpen up on old school (read: non-React) web development because I feel like us JavaScript devs can hyperfocus on the popular tech stacks way too often.

That's when I decided I would do what our forebears did and write HTML by hand.

(audience gasps)

Neocities is pretty cool

I know the indie web has been taking off lately and I absolutely love it. I tried to maintain a Neocities page a while back, but as it turns out writing real HTML-by-hand is very annoying. It's not the syntax either – it's the fact that I got so used to having a toolchain, hot reloads, preprocessors and the like that writing web pages as they are rendered by the browser feels like writing on a cave wall.

The idea is there, though: it's why this site looks like it does, even though it runs on Next and Tailwind. It's about reclaming the personality the web used to have. I'm trying, but I have developer brain and inevitably make sites look bland and modern. Bleh.

The point I'm trying to make here is something else: sites like Neocities and ideas like an "indie web" have inspired me to try and use my web knowledge to something more than my employers' cloud-heavy, enterprise web apps.

CSS is the best

CSS is much better at formatting than most other 'easy' tech. I've been tinkering with game engines and styling and theming UI on them is probably the worst part. Word processors have always been a gripe of mine with their limited formatting options too; I've tried basically all of them. I still use Google Docs from time to time for basic stuff, but that's it: of all word processors out there, the only one that's useful is the simplest one! Anything else is easier formatted with LaTeX or CSS.

And while LaTeX is probably the best option if we're dealing in absolutes, perhaps because I never needed it for academic reasons I find its toolchain sort of difficult to leverage. CSS however has become completely portable. Media queries allow us to style for print and any screen we want and give us near-full control over layout and appearance.

The project

I wanted to create a resume that catches the eye of whoever gets hold of it. For that purpose I set some goals:

  • It has to look somewhat authentic and different from raw HTML
  • The page needs to be print-friendly
  • It still needs to look like a resume

The first point is fairly easy: I can really do whatever as long as it remains readable. This is usually where I have the most fun. Recently I visited the São Paulo Picture Gallery and see the work of Lygia Clark, a neoconstructivist painter and installation artist. I was particularly intrigued by her Modulated Space and Breaking of the Mold series of paintings, and I wanted to use raw geometry to create texture. I absolutely did not succeed, but I liked the end result nonetheless.

Print-friendly pages are surprisingly easy to style with media queries. The browser takes care of a lot of stuff – like printer default margins and white backgrounds – by default while allowing the page designer to tweak these values and pick and choose from the web layout. Surprisingly, I didn't need to do a lot of screen- or print-exclusive styling, and just being mindful of both while writing CSS was mostly sufficient.

Still, for a long stretch of the page's development, I kept the print page light-coloured, thinking about real printers more than PDFs you send to people. I changed my mind halfway through and the print version is now dark as well. I honestly can't recall the last time I printed my CV.

Sass is kind of obsolete

When I started writing this project, I chose to use SCSS for the preprocessing power of variables, mixins and (hopefully careful) nesting. While I knew CSS variables existed for a long time, I thought they hadn't taken off for whatever reason – I just made the assumption there would be massive caveats to using them, because that's how the web used to work (you know, a decade ago).

I ended up using about three mixins: one for a border gradient setup that used a few properties, one for grid layouts (something I had completely dropped in favour of flexbox for most tasks), and another couple ones that I probably didn't need, considering I barely remember what I used them for.

I was already on the fence about using SCSS for such small things. I don't even like Sass, it's just the fact that it allows for cleaner CSS with its nesting and loops.

Then I learned one of these things that you never really hear about unless you're keeping up with all the web news: browsers now support CSS nesting! Sure, about 12% of the web still doesn't support it, but a quick look at the Can I Use page shows us that's mostly old iPhones and Chrome versions. I wouldn't use it in an enterprise app just yet, but it's good enough for a page you only expect tech folk to access (and in a work context no less). If that's not enough reason, there's also the fact that this page is probably only getting accessed by myself 99% of the time, since recruiters usually require PDF files, which I'll generate from my own browser using the print dialog.

I finished writing the app using SCSS since I had already started, but I quickly decided I was going to move to pure CSS or maybe CSS in JS.

Static rendering is extra

If I had gone the CSS in JS route, there would be a synchronicity issue since I'm not using server-side rendering. I could go down the rabbit hole of preprocessors and code generators and use something that would render static HTML and CSS from Styled Components or Emotion, but that felt like overkill. I am using a basic Vite template with out-of-the-box transforms and I don't plan to make a complex toolchain for such a tiny project.

With this in mind, I can use CSS in JS for web-only stuff (more on that later), but wrapping all CSS in Emotion or Styled Components would mean it starts loading later than the rest of the page since that CSS only gets generated at runtime. Plain CSS it is.

I did make an experiment with Emotion at this point in the process, but the only way I could get it to build a static CSS was through preprocessing with a script, at which point I wouldn't even need Vite. I may do this later for the knowledge, but it's not worth it now.

SEO-friendly even if no one is looking

One of the biggest mistakes of modern web dev is forgoing SEO just because a page is not meant to be landed on. Sure, it feels nice sticking it to the robots, but it's not only marketing aficionados and CEOs that should care about easily-crawlable pages. You want to make a light and modern-browser-optimised page because it enhances accessibility and reduces load times for real users.

I'm not saying my page is perfect (I've been SPA-pilled for a long time and old habits die hard), but caring about that stuff is a step in the right direction. It being a small project, I decided I'd just stop being lazy and write good old repetitive HTML instead of React or Markdown or Pug or whatever else. Not only I can tweak the end result directly, it just felt more like the old days I barely got to see.

What I learned

It felt weird just going to index.html and editing it like it's 1997. I was born that year! Even fresh out of school I was already being introduced to MVC frameworks (mainly Zend, later Rails, Django and Laravel), so the idea of pushing plain HTML to a finished product was pretty weird. It's not that I had never done that (the early versions of my website in 2017 were on GH Pages without any preprocessing), but I sure hadn't ever since I went pro. (Next is so easy to set up, why would I?)

I used Chrome's Lighthouse feature to evaluate my page performance. To my surprise, I managed to make a plain web page score under 90! That's when I knew I was really behind the times.

Optimisations

HTML <link> tags aren't very optimised by default as it turns out. While Vite would allow me to use CSS @imports and inline the imported stylesheets for me, loading the non-critical ones from other files was not as simple as I'm used to.

If you learned HTML later than I did, you are probably aware of this, but alas I was not. Links with rel=preload will load before the page is rendered, reducing layout shift.

<link href="src/styles/typography.css" rel="stylesheet">

<!-- ...becomes... -->

<link href="src/styles/typography.css" rel="preload" as="style">

There apparently is a trick using media="print" to prevent the browser from blocking for non-critical stylesheets, but I didn't think that was necessary in my scenario. I found that one must strike a balance between load times and initial layout shift (allowing more resources to load async will make the layout shift more as new styles are applied).

External resources (e.g. CDNs) may load faster if you use rel="dns-prefetch" to resolve domain names early.

<link rel="dns-prefetch" href="https://fonts.googleapis.com">
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=Quattrocento:wght@400;700&family=JetBrains+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap">

I finally used Web Components!

I'm not going to say they're good. I don't know if I'll use them again. But I didn't want to include a JavaScript library in the project for a couple interactive buttons that aren't critical. The point of this page is to be as raw as possible without sacrificing developer experience.

I tried Web Components for the first time around 2019. Looking back, that was super early, and if the tech was around 5 years ago and still isn't favoured, that definitely does not bode well for it as a standard.

Nonetheless, I thought it would be nice to check in on it and see what it's like today. Turns out for my basic use case it was pretty neat!

I created custom elements like these for collapsible sections and a back to top button. Here's the collapse button for instance:

class Collapse extends HTMLElement {
  connectedCallback() {
    this.addEventListener("click", () => {
      const collapsibleId = this.getAttribute("collapsible");
      if (!collapsibleId) return;

      const collapsible = document.getElementById(collapsibleId);
      if (!collapsible) return;

      collapsible.toggleAttribute("collapse");
    });
  }
}

customElements.define("cv-collapse", Collapse);
// Can be used in HTML as <cv-collapse collapsible="target-id">

If I was working on a state-heavy UI I feel like it probably wouldn't be as easy as a fully reactive library. It's very nice for small interactive functionality though – the big difference from component-based libraries is that the content is still meant to be written in HTML.

I had managed to create a serviceable inheritance system for styled web components using Emotion, but ended up not using it for reasons I outlined earlier. Here's what that looked like:

class WebComponent extends HTMLElement {
  connectedCallback() {}
}

// API
class StyledWebComponent extends WebComponent {
  static of(...classNames: string[]) {
    return class extends this {
      connectedCallback() {
        super.connectedCallback();

        this.classList.add(...classNames);
      }
    };
  }
}

// Basic styled components
const Bordered = StyledWebComponent.of(css`
  border: 2px solid var(--border-color);
  border-image: var(--border-gradient) 1;
`);

// Inheriting and adding new styles to components
class BackToTopButton extends Bordered.of(css`
  display: flex;
  justify-content: center;
  align-items: center;
  grid-area: b;
  background: none;
  font: inherit;
  color: inherit;
  box-shadow: none;
  height: 12em;
  margin: 6em 0;
  cursor: pointer;
  transition: all 300ms ease;

  &:hover {
    background: var(--hl);
    color: black;
  }

  @media print {
    display: none;
  }
`) {
    // Interactivity goes here...
}

While I enjoyed using these components, the Custom Elements API is, as far as my knowledge goes, only usable on the client side. This would bring the layout shift problems from earlier back with a vengeance. The only way to circumvent this would be baking the Emotion class names into the served HTML, which is really only doable with a more powerful SSR setup, and hasn't been done with Web Components since at this point you might as well just use React, Vue or Svelte.

Grid collapsibles!

My favourite finding for this project was a JavaScript-light solution for animated collapsible sections. In the past, all solutions I'd seen were either hacks using absurdly large max-height values or calculating element heights with JavaScript then smoothly computing each animation frame using requestAnimationFrame. Neither of which really felt right to me, but I came across an amazing solution on Stack Overflow that uses grid behaviour to solve this. Unfortunately I can't find it anymore, but shoutout to the guy who suggested it a few years ago to another guy I don't know!

The idea is that grids are meant to accomodate their children within their layout, and by leveraging the fr unit (meaning "fraction (of the grid)") with a single element we can essentially animate from full to null.

cv-collapsible {
  position: relative;
  display: grid;
  grid-template-rows: 1fr;
  transition: all 600ms ease;

  &[collapse] {
    grid-template-rows: 0fr;
  }

  & > * {
    overflow: hidden;
  }
}

Initially I made it so the print page had very little style applied to it – I played it safe by wrapping most of the styling in @media screen. While that worked out fine, there was some stuff that had no place in a professional PDF or printed CV, such as the aforementioned back to top button and an indicator I had added to the collapse triggers for clarity.

Like I pointed out before, I don't really plan for this page to be landed on very often – not even linked to by myself, since I'll be mostly sending the PDF around. At some point during development I realised all the styling effort I was ignoring print for was kind of wasted.

I then found out that you can remove the printer-default margins and headers using the OS or browser dialog, so having a dark background or things that are supposed to be at the edge of the page (I had added borders to the edge of the root node) could look OK in print. Next, I reminded myself that the resume is going to get passed around as a PDF way more often than print. I'll add a printer-friendly layout somewhere sometime, but for now I'm OK exporting to a dark PDF.

The result

This is what my CV looks like now. You can check out the source code on the repo to get inspired or maybe even fork it.

The key takeaway is that I'm probably going to use HTML for print a lot more going forward. On a more work-relevant note, I should keep up with browser CSS support more closely.

Web Components are probably still not very useful, but it was nice to use something similar to custom components in vanilla JS and HTML.

Sass lasted way longer more than any other preprocessor and its influence in the CSS standard and newer preprocessors cannot be overstated, but as its features get adopted by other projects it's starting to become unnecessary.