Skip to main content

How I built this website

Sharing the decisions I made and the tools I used to create this website.

The design and code are handcrafted by me. I use Figma as my design tool. I use Visual Studio Code as my code editor.

I like to use my personal website as a way to experiment and learn. Over the years, I've redesigned (and refactored) it numerous times. I've built it in Angular and React, and used static site generators like Jekyll and Gatsby.

Although not every iteration makes it to production, it's still exciting to experiment with new frameworks, browser APIs, and technologies.

Tech Stack

For the local development process, I use Parcel. The front-end code is written in Pug and Sass. The (minimal) JavaScript is 100% Pure Vanilla™ JavaScript. The website itself is hosted on Netlify.


For the process of designing the site, I started with larger viewports to make sure I wasn't limiting my imagination for what the experience could be like.

Although I designed with larger viewports in mind first, I developed from a mobile-first perspective. (My media queries use min-width.)

Deployment Process

The deployment process kicks off with me pushing changes to GitHub. A build then gets triggered on Netlify.

I have 2 plugins enabled for my site: one for checking the accessibility of my pages in case I missed anything silly, and another for checking that I don't have any dead links.

Main Branch

For new commits on my main branch, a production build is triggered. This runs a build script in my package.json that turns my pre-compiled code into compiled code that I can serve in the browser.

Feature Branches

For feature branches, I configured Netlify to spin up a non-production deployment so I can test my changes. This runs the same build script in my package.json as for the main branch.


Framework vs. Frameworkless

Although I've built the site with a JavaScript framework before, sometimes I like to go back to simpler ways of creating websites.

Since this iteration of my personal site has very little functionality, I decided to stick to an HTML templating language instead of inflating my bundle size with a framework. The templating language compiles into pure HTML, so there's no increase in the HTML sent to the client.

CSS vs. Sass

Although I could have gone with pure CSS for styling, I still opted for Sass. The convenience of nesting styles and creating mixins still edged out CSS. It's not all about variables, after all. In terms of bundle size, Sass doesn't—though it can—increase the size of the CSS delivered to the browser.

I also opted for the SCSS syntax, as I feel most comfortable working in that way. Funny enough, though, the Sass syntax is closer to the benefit you get from writing Pug instead of HTML, so maybe I'll make the switch one day.

JavaScript vs. TypeScript

Since I knew I'd write pretty minimal JavaScript, this was an easier decision. I decided to stick with vanilla JS since there was really no reason to take advantage of types or interfaces.


I decided to pay a bit more attention to the performance of my website this time around. It's an odd to start caring when it's pretty minimal code, but I wanted to dive deeper into performance—both perceived and actual. Specifically, I wanted to look into everything to do with compositing.


I started with all my JS inside a single file. Then, I split out the code required to check if the user had a theme preference so that I could set a class on the body. With this approach, I avoid showing people a flash of the light theme before the dark theme is applied (if that's their preference). This JS is an intentionally blocking script to make sure that body class is there when the CSS is being interpreted by the browser.

Speaking of blocking JavaScript, when I split out that theme-checking JS into its own block file, I forgot to add a defer attribute to my JS that I didn't want to block rendering. So, I did that.

I've always used Google Analytics on my personal websites. Since paying more attention to performance and realizing I never check my analytics, I determined it was a waste and removed it. Although this wasn't render-blocking JavaScript, I still saved a network request.


I have two different grid images for the background: one for light theme and one for dark theme. These are set as the body's background-image. Only the background image that's used in the initial render will get requested once the browser starts parsing CSS, which is a boon for performance.

Since the background-image is set differently depending on the theme, only one image is requested at a time. If I toggle on the dark theme, the second image is requested right away. This seemed ideal and so I left it as-is.


Although they're SVGs, I originally used my blob images with an img tag and pointed to their asset path, meaning I was making additional requests for images.

Since I want to improve my first contentful paint, I instead inlined the SVGs in my HTML to increase load speed and save ~1KB (50%) in network requests.

Related to this improvement, I encountered a strange bug with Pug not allowing attributes I set on svg elements to get passed through to the generated HTML. Because of this, my blobs looked odd on some devices since the viewBox wasn't getting set on the SVG.

Since this was a problem with the templating engine I was using, I decided to use overflow: hidden on my SVGs instead of switching back to the img tag approach.

Go back home