You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Download each web font once on a first visit #104392
Three things load the 10 Expensify woff2 fonts on web:
The fixed-name copy in /fonts/. The web build copiesassets/fonts/web there. fonts.css and two inline @font-face rules in index.html point to it. The inline rules were added in Perf web windows fonts improv so the splash screen's fonts start downloading while the HTML is parsed. The browser draws all of the app's HTML text with these files.
The hashed copy. Since June 2026, chartFontAssets.ts imports the same fonts, and the bundler emits them a second time under names that include a content hash. Charts are drawn by Skia on a canvas, which can't use CSS fonts, so Skia fetches these files itself for chart labels.
The service worker. Workbox precaches every file for offline use. It fetches unhashed files past the HTTP cache and takes hashed ones from it.
The font preload filter matches both copies, so production index.html preloads each font under two URLs.
Problem
When a browser has no NewDot files cached yet, if the user opens any NewDot page on web, then the page downloads each Expensify woff2 font twice before the splash screen hides, and the service worker downloads it a third time. So 374 KB of fonts costs about 1.1 MB of traffic.
Solution
Give each of these fonts one hashed URL:
Import fonts.css from the web entry so the bundler processes it. This points its url() values at the hashed files the charts already use. Stop copying the fonts to /fonts/.
Write the hashed URLs into the two inline rules in index.html, which is already a build-time template, so the splash screen's two Expensify Neue faces still start downloading while the HTML is parsed.
Each woff2 font is then downloaded once, and the service worker takes it from the HTTP cache.
Impact: the sign-in page hides its splash screen about 160 ms (4%) sooner.
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.23-3 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
Yes, it worked. Production (9.5.23-3) now references each Expensify woff2 font under exactly one hashed URL, so a first visit downloads each font once. I checked the deployed production files directly. I couldn't capture a live browser network trace, because the web tester only drives the dev server and can't record HAR or service worker traffic.
index.html preloads each font once10 font preloads, one per font, all hashed (for example, /ExpensifyNeue-Regular.bfe6e44ce4.woff2). No /fonts/ preloads, no inline @font-face rules, and no /css/fonts.css link.
✅
Bundled CSS uses the same hashed URLs/main.c338bcf09f.css holds the @font-face rules. Every url() matches a preload URL exactly, so the preload and the CSS share one download.
✅
Service worker reuses the HTTP cacheThe Workbox precache lists only the hashed fonts, each with revision:null. Workbox fetches those without cache-busting, so the browser serves them from the HTTP cache. No /fonts/ entries remain.
✅
Live first-visit network traceNot run. The web tester can't open production in a fresh profile or record network traffic.
⚠️
One note: /fonts/*.woff2 and /css/fonts.css still return 200 on production. They look like leftover files from earlier deploys. Nothing in the current HTML, CSS, JS, or service worker requests them, so they don't add traffic.
Activity
roryabraham commentedon Oct 21, 2026
@collectioneur when do you estimate you'll open a PR for this?
collectioneur commentedon Oct 22, 2026
I was a little tied up with other tasks, I'll open the PR today 🫡
collectioneur commentedon Oct 22, 2026
@roryabraham The PR is up 🙂
melvin-bot commentedon Oct 27, 2026
@roryabraham @collectioneur
The solution for this issue has been 🚀 deployed to production 🚀 in version 9.5.23-3 and is now subject to a 7-day regression period 📆. Here is the list of pull requests that resolve this issue:
roryabraham commentedon Oct 27, 2026
@MelvinBot test this change in NewDot production - did it work?
roryabraham commentedon Oct 27, 2026
I opted out of C+ review on the PR, so no payments will be due
MelvinBot commentedon Oct 27, 2026
🤖 Web test steps verification results:⚠️ partial
Yes, it worked. Production (9.5.23-3) now references each Expensify woff2 font under exactly one hashed URL, so a first visit downloads each font once. I checked the deployed production files directly. I couldn't capture a live browser network trace, because the web tester only drives the dev server and can't record HAR or service worker traffic.
Production runs the fix
new.expensify.com/version.jsonreturns9.5.23-3, the release that includes Download each web font once on a first visit.
10 font preloads, one per font, all hashed (for example,index.htmlpreloads each font once/ExpensifyNeue-Regular.bfe6e44ce4.woff2). No/fonts/preloads, no inline@font-facerules, and no/css/fonts.csslink.Bundled CSS uses the same hashed URLs
/main.c338bcf09f.cssholds the@font-facerules. Everyurl()matches a preload URL exactly, so the preload and the CSS share one download.Service worker reuses the HTTP cache
The Workbox precache lists only the hashed fonts, each withrevision:null. Workbox fetches those without cache-busting, so the browser serves them from the HTTP cache. No/fonts/entries remain.Live first-visit network trace
Not run. The web tester can't open production in a fresh profile or record network traffic.One note:
/fonts/*.woff2and/css/fonts.cssstill return 200 on production. They look like leftover files from earlier deploys. Nothing in the current HTML, CSS, JS, or service worker requests them, so they don't add traffic.