← Work

1 / 77

Case 01 Creator Sep 2026 – Present

GameKal

A calendar for the games you mean to play: one Expo codebase serving web, iPhone, iPad and Android, fed by a release catalog drawn from nine storefronts.

  • React Native
  • Supabase
  • Vite
  • Storybook
  • Three.js
  • Swift
  • React
  • TypeScript
  • Vercel
GameKal game page for The Legend of Zelda: Ocarina of Time: tracking controls, a screenshot gallery, release dates and similar games
Games in the catalog
About 147,000 scraped from nine storefronts
Home page load
1.81 s down from about 20 s
Warm title search
1.1 ms median, down from 19.6 ms
First search on a cold instance
183 ms down from 572 ms
Search response
1.8 KB down from 42 KB
Icon font
8 KB down from 573 KB
Catalog function
35 MB down from 640 MB
Automated tests
538
Commits
130
Current release
0.1.25, November 10, 2026
First commit
September 24, 2026
Platforms
Web, iOS, Android
Release line
0.1.0 to 0.1.25

01

Notes from the build

01 / 10

Fonts cut to what's drawn

Fonts are self-hosted woff2 subsets. The icon font ships 8 KB of the glyphs the app uses, down from 573 KB.

02 / 10

Lighthouse

The production site scores 100 for accessibility and SEO. Performance started at about 84 on a wide window and about 61 on a phone.

03 / 10

A 78 MB catalog, opened in pieces

The live release file is 78 MB, so requests are answered from small per-year files and built indexes, and tests record every file a lookup opens.

04 / 10

Public data as files, not a function

Every catalog read used to run a bot-checked serverless function marked private, no-store. Days, counts and games are now static files on Vercel's CDN, built at deploy under a content stamp so everything beneath it caches forever and only current.json is ever rechecked. Home went from about 20 s to 1.81 s.

05 / 10

147,000 games in 8,192 buckets

A file per game was too many, so every game id hashes with FNV-1a into one of 8,192 bucket files of about 10 KB. The server and the browser share the same gameBucket function, and a test compares every game's file against the API's answer.

06 / 10

59 bytes that staled every index

Two small data fixes grew the live file by 59 bytes. Every index is stamped from file sizes, so all of them read as stale and the API fell back to parsing all 78 MB per request, 5 to 8 s each, while picks returned 503. Deploys now refuse to ship a stale index, checked before and after the catalog syncs.

07 / 10

640 MB down to a 35 MB function

Picks, Similar Games and title search depend on the person or the query, so they moved to a small compute function that carries only the three indexes it reads. Its answers are cached at the CDN for a day, and Similar Games came back in 50 ms.

08 / 10

Retries that buried a cold instance

The client resent any request after 8 s while the first kept running. With Fluid compute sharing one Node instance, each resend queued behind JSON parsing until the instance was buried for about 50 s. Requests now wait 45 s for the catalog and 30 s for search, share one request per URL, and honor Retry-After on a 429.

09 / 10

Profiled on the phone, not the desktop

A 41-second Hermes CPU profile found 8.3 of 26 busy seconds in wishlist matching, which built two URL objects per comparison, and on the phone URL is a slow JavaScript polyfill. A one-regex link reader, animations moved to the UI thread with Reanimated, and a removed swipe gesture took the post-load freeze from 5.8 s to about 0.9 s and tab switches to 36 to 75 ms.

10 / 10

One temp folder, two names

The suite passed 538 of 538 in Linux and 537 on a Mac. On macOS the temp folder lives under /var, a link to /private/var, so a test that records every file a lookup opens saw the right file under the wrong name. Resolving the real path fixed it, and four negative checks that could never fail on a Mac can fail again.

02

What it's written in

  1. TypeScript 65%
  2. JavaScript 33%
  3. CSS 1.4%
  4. Python <1%
  5. Shell <1%
  6. SQL <1%
  7. Swift <1%

Bytes of source in src, web, scripts, server, api, supabase and modules, measured November 10, 2026. Excludes dependencies, build output and the JSON release catalog (about 840 MB of data).

03

The stack 24

App

  • Expo SDK 57
  • React Native 0.86.3
  • React 19.2.3
  • TypeScript 6.0
  • react-native-web 0.21
  • Reanimated 4.5 UI-thread animations
  • Gesture Handler 2.32
  • react-native-svg 15.15

Native

  • Custom Expo module notch-side, written in Swift
  • expo-notifications 10 a.m. release reminders
  • expo-image-picker custom case art for added games

Backend

  • Vercel Functions /api/releases, /api/compute, /api/report, /api/wishlist
  • Static catalog on the Vercel CDN
  • Supabase Postgres, Google and Discord sign-in, storage
  • Vercel BotID and Firewall API protection

Catalog pipeline

  • Store scrapers Steam, Epic, Battle.net, Ubisoft, GOG, Nintendo US and EU, PlayStation, Xbox
  • Wikipedia and history backfills Nintendo, PlayStation and Xbox release history
  • Built search and picks indexes

Internal tools

  • Storybook 10.6 with the accessibility addon
  • VitePress 1.6 project docs and blog
  • Mermaid 12 live ecosystem map
  • Three.js 0.186 3D extrusion of the ecosystem map
  • CodeMirror 6 and TanStack Table the catalog content editor
  • Vite 8

04

How it ships Custom Pipeline Built

The pipeline is mine end to end. Vercel's Git integration is disconnected on purpose, so a push never deploys: the website ships from a machine that holds the catalog, and the phone builds go through EAS.

  1. 01 Checks

    node --test, tsc for the app, the CMS and the ecosystem map, and boundary checks that keep web-only code out of the phone apps

  2. 02 Release prep

    /prepare-release bumps the version, updates the changelog and every doc that cites it, and verifies the search index, regions and native project sync

  3. 03 Web

    expo export, a static 404 page and a static catalog build, published to Vercel

  4. 04 iOS and Android

    EAS production builds, shipped to TestFlight and the Play Console

I'm listening if you're talking

A bug, a new build, a data question, a site that needs to be faster or a full rebuild. Tell me what's going on.

Let's see how to collaborate.