Beerfolio

Beerfolio

Website Development

Project Description
  • A fifteen-year beer label collection, scanned and rebuilt as a searchable web app so I stop buying the same beer twice.
  • Two views over one dataset: an infinite-scrolling label grid and a clustered world map of every brewery in the collection.
  • No database and no API. The entire app runs on two static JSON files and does all filtering, grouping and counting in the browser.
  • Labels are added and edited from the site itself, on a phone, standing in the aisle.
  • Sole designer and developer; also my playground for trying new front-end techniques on something I actually use.
Tools

React 19

TypeScript

Vite

Tailwind

Leaflet

Framer Motion

Photoshop

Launch Website
Visit Site

Case Study

Problem

The collection lived in a physical portfolio book at home and on an aging PHP site that was effectively unusable in a grocery store. Finding out whether I already owned a beer meant scrolling a static gallery on a phone; the brewery map was built on Google Maps, which meant an API key, a billing account and a page that counted beers by fetching all 245 brewery pages on every single load. Worst of all, adding a label was a desk job: crop the scan, export three image sizes, name each one by hand, upload them, then edit the data to match. The book always ran months ahead of the site.

Solution

A React and TypeScript rebuild driven by two static JSON files. Search, filters, grouping and per-brewery beer counts all happen in memory, so the app makes one request for the data and none after that. The Google Maps layer became Leaflet with OpenStreetMap tiles, with no key and no billing, and it lazy-loads, so the map costs nothing until you open it. Adding a label became a single dialog on the phone that took the photo: crop, three fields, save. The browser does the cropping, the resizing and the file naming.

The Collection

1,355

Beer labels

336

Breweries mapped

27

Countries

102

Beer styles

The Beerfolio label grid, showing beer labels with brewery, style and year
Every Label, One Grid

Labels render as a responsive grid with infinite scroll and WebP thumbnails at 1x and 2x. Each card carries the brewery, style and the year the label joined the collection, the three things I actually sort by.


The physical Beerfolio label book
Where It Started

Beerfolio began as an expandable portfolio book on a shelf: peel the label off the bottle, press it flat, slide it into a sleeve. The book is a great conversation piece and it is why guests keep contributing labels, but it has one flaw: it stays home. The site exists to bring the book to the store, and every label in it is a scan of the physical page.

Searching the collection for stout, narrowing 1,355 labels to 157
Answering the Aisle Question

The whole app is judged on one interaction: I am holding a bottle, and I need to know in a couple of seconds whether it is already in the book. Search runs across name, brewery, style and year at once, and the brewery, style and year dropdowns re-scope themselves to whatever is left. Searching 'stout' drops 1,355 labels to 157 across 75 breweries. Because everything is already in memory, results land as fast as I can type.

The Add a label dialog, with a drag-and-drop image target and a brewery field autocompleting to Sierra Nevada
Three Files Per Label

Every label in the collection is three image files, not one: the full-size scan, a WebP thumbnail, and a 2x WebP for retina screens. That is why 1,355 labels come to exactly 4,065 images. Producing that set by hand meant cropping to 3:4, exporting three sizes and naming all three brewery-year-title so nothing collides, which was the real reason labels piled up unadded for months. Now the whole set comes out of one dialog: drop in a photo and the browser generates both thumbnails on a canvas at 220px and 495px wide.

The add dialog with a label loaded: a live 3:4 crop slider, and the brewery field suggesting Sierra Nevada
Getting the Crop and the Spelling Right

Two details do most of the work here. The crop is a live 3:4 preview you drag or nudge with a slider, so framing a label is a two-second gesture instead of a round trip through an image editor. The bias is stored on the label, so it can be re-cropped later without the original ever being touched. The brewery, title and style fields autocomplete from what is already in the collection, which sounds cosmetic and is not: brewery name is the join key to the map, so typing 'Sierra Nevada Brewing' instead of 'Sierra Nevada' would silently drop a pin off the map.

Editing a label: the crop slider, metadata fields, and Save changes button over the label details drawer
Save Changes Is the Whole Deploy

Filenames are derived from brewery, year and title, so correcting a typo used to mean renaming three files and repointing the record at them, which is exactly why typos stayed. The editor now works out what actually changed: adjust only the crop and it regenerates the two thumbnails and leaves the original alone; change the title, brewery or year and it re-uploads under the corrected filename so the file and the record can never drift apart. One multipart POST goes up, labels.json is rewritten about 700ms later, and there is no publish step at all.

Beerfolio's clustered world map of every brewery in the collection
Rebuilding the Map

The old brewery map was Google Maps, which meant an API key and a billing account for a personal side project. I ported it to Leaflet over OpenStreetMap and CartoDB dark tiles, added marker clustering so 336 pins across 27 countries stay readable at world zoom, and lazy-loaded the whole thing with React.lazy. Leaflet only downloads when you open the map, which keeps the initial bundle at roughly 356 KB instead of 548 KB.

A brewery marker popup showing Sierra Nevada's logo, location and beer count
One Join, No Backend

Brewery name is the join key between the two JSON files, and that single decision is the architecture. Beer counts per brewery are grouped out of the label data at runtime, so the map needs no extra data and makes no extra requests. Sierra Nevada's 92 is computed, not stored. The old site's 245 requests per page load are simply gone. Markers render each brewery's logo as a CSS background image, and clicking 'View labels' filters the grid to that brewery and drops you back into it.


Other Projects