From 7b43b443a54516d8d2a7fc323028538c5b6bcfcf Mon Sep 17 00:00:00 2001 From: Rishi Raj Jain Date: Sun, 12 Jul 2026 16:10:10 +0530 Subject: [PATCH 1/6] Update package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 088deb953..5af699424 100644 --- a/package.json +++ b/package.json @@ -152,6 +152,7 @@ "tsx": "^4.21.0", "typescript": "^6.0.2", "vite": "^8.0.13", + "vite-font": "^0.3.2", "wrangler": "^4.102.0" }, "engines": { From 0e15dc067b600f3e077ce851c336bebdae41ad96 Mon Sep 17 00:00:00 2001 From: Rishi Raj Jain Date: Sun, 12 Jul 2026 16:10:21 +0530 Subject: [PATCH 2/6] Update pnpm-lock.yaml --- pnpm-lock.yaml | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8d2a7cb49..eaffad83a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -367,6 +367,9 @@ importers: vite: specifier: ^8.0.13 version: 8.0.13(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(tsx@4.21.0)(yaml@2.9.0) + vite-font: + specifier: ^0.3.2 + version: 0.3.2(vite@8.0.13(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(tsx@4.21.0)(yaml@2.9.0)) wrangler: specifier: ^4.102.0 version: 4.103.0 @@ -4052,6 +4055,9 @@ packages: resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} engines: {node: 18 || 20 || >=22} + brotli@1.3.3: + resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==} + browserslist@4.28.2: resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -4156,6 +4162,10 @@ packages: cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -4480,6 +4490,9 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dfa@1.2.0: + resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==} + diff@8.0.4: resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} engines: {node: '>=0.3.1'} @@ -4840,6 +4853,9 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + fontkit@2.0.4: + resolution: {integrity: sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==} + for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} @@ -5870,6 +5886,9 @@ packages: package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} @@ -6233,6 +6252,9 @@ packages: engines: {node: '>= 0.4'} hasBin: true + restructure@3.0.2: + resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==} + rimraf@2.6.3: resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -6597,6 +6619,9 @@ packages: three@0.183.2: resolution: {integrity: sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==} + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + tinyexec@1.0.4: resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==} engines: {node: '>=18'} @@ -6720,6 +6745,12 @@ packages: unenv@2.0.0-rc.24: resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} + unicode-properties@1.4.1: + resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==} + + unicode-trie@2.0.0: + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + unicorn-magic@0.4.0: resolution: {integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==} engines: {node: '>=20'} @@ -6857,6 +6888,12 @@ packages: resolution: {integrity: sha512-elFXkF9oGy4CJEeOdP1DSEHRDKWfmaEDfT9/GuF4jmyfmUF6nC//iN+ythqMEVvrtchOEHGKLumZwnu1NjoI0w==} hasBin: true + vite-font@0.3.2: + resolution: {integrity: sha512-g2fiAIyllkcxxnXvisNwXcCw/R3A/XrHLg7L+smvgwyGSLgRV+e7xyp0QuOn7SyXiIc/gMG0clF8sbiIZwVfXA==} + engines: {node: '>=20.0.0'} + peerDependencies: + vite: '>=5' + vite@8.0.13: resolution: {integrity: sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -10477,6 +10514,10 @@ snapshots: dependencies: balanced-match: 4.0.4 + brotli@1.3.3: + dependencies: + base64-js: 1.5.1 + browserslist@4.28.2: dependencies: baseline-browser-mapping: 2.10.13 @@ -10599,6 +10640,8 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clone@2.1.2: {} + clsx@2.1.1: {} cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): @@ -10936,6 +10979,8 @@ snapshots: dependencies: dequal: 2.0.3 + dfa@1.2.0: {} + diff@8.0.4: {} discord-interactions@4.4.0: {} @@ -11374,6 +11419,18 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + fontkit@2.0.4: + dependencies: + '@swc/helpers': 0.5.18 + brotli: 1.3.3 + clone: 2.1.2 + dfa: 1.2.0 + fast-deep-equal: 3.1.3 + restructure: 3.0.2 + tiny-inflate: 1.0.3 + unicode-properties: 1.4.1 + unicode-trie: 2.0.0 + for-each@0.3.5: dependencies: is-callable: 1.2.7 @@ -12717,6 +12774,8 @@ snapshots: package-manager-detector@1.6.0: {} + pako@0.2.9: {} + pako@1.0.11: {} parse-entities@4.0.2: @@ -13089,6 +13148,8 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + restructure@3.0.2: {} + rimraf@2.6.3: dependencies: glob: 13.0.6 @@ -13600,6 +13661,8 @@ snapshots: three@0.183.2: {} + tiny-inflate@1.0.3: {} + tinyexec@1.0.4: {} tinyglobby@0.2.15: @@ -13730,6 +13793,16 @@ snapshots: dependencies: pathe: 2.0.3 + unicode-properties@1.4.1: + dependencies: + base64-js: 1.5.1 + unicode-trie: 2.0.0 + + unicode-trie@2.0.0: + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + unicorn-magic@0.4.0: {} unified@11.0.5: @@ -13853,6 +13926,11 @@ snapshots: vite-bundle-analyzer@1.3.6: {} + vite-font@0.3.2(vite@8.0.13(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(tsx@4.21.0)(yaml@2.9.0)): + dependencies: + fontkit: 2.0.4 + vite: 8.0.13(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(tsx@4.21.0)(yaml@2.9.0) + vite@8.0.13(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.7.0)(tsx@4.21.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 From 45f1be75f857038d84d097ae4861a8a5081d5b86 Mon Sep 17 00:00:00 2001 From: Rishi Raj Jain Date: Sun, 12 Jul 2026 16:10:31 +0530 Subject: [PATCH 3/6] Update vite.config.ts --- vite.config.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/vite.config.ts b/vite.config.ts index 918ac94c3..5e9d5ab06 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,6 +8,7 @@ import { tanstackStart } from '@tanstack/react-start/plugin/vite' import tailwindcss from '@tailwindcss/vite' import { cloudflare } from '@cloudflare/vite-plugin' import { analyzer } from 'vite-bundle-analyzer' +import viteFont from 'vite-font' import viteReact from '@vitejs/plugin-react' import fs from 'node:fs' import { createRequire } from 'node:module' @@ -29,6 +30,20 @@ const shouldBuildSourcemaps = shouldUseSentryPlugin || process.env.BUILD_SOURCEMAPS === 'true' const SITE_URL = 'https://tanstack.com' +// vite-font measures the real Inter metrics and emits a fallback @font-face +// under this name. It has to be spelled the same way in the `--font-sans` +// stack in app.css, or the metric overrides never apply. +const INTER_FALLBACK_FAMILY = 'Inter Fallback' + +// Inter ships pre-subset in public/fonts, so the ranges have to be declared +// by hand — they are what makes the browser skip latin-ext on a latin page. +const INTER_UNICODE_RANGE = { + latin: + 'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD', + latinExt: + 'U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF', +} + const localEnvPath = path.resolve(__dirname, '.env.local') const defaultCheckoutEnvDir = path.join(os.homedir(), 'GitHub/tanstack.com') const envDir = @@ -331,6 +346,37 @@ export default defineConfig({ : []), contentCollections(), tailwindcss(), + // Start renders its own , so there is no HTML entry to transform. + // `__root.tsx` imports the generated tags from `virtual:vite-font` instead. + viteFont({ + injectHtml: false, + config: [ + { + name: 'Inter', + fallback: 'sans-serif', + fallbackName: INTER_FALLBACK_FAMILY, + display: 'swap', + src: [ + { + path: 'public/fonts/Inter-latin.woff2', + style: 'normal', + weight: '100 900', + preload: true, + css: { 'unicode-range': INTER_UNICODE_RANGE.latin }, + }, + { + // Every page is latin; latin-ext is fetched on demand via + // unicode-range, so preloading it would just cost bandwidth. + path: 'public/fonts/Inter-latin-ext.woff2', + style: 'normal', + weight: '100 900', + preload: false, + css: { 'unicode-range': INTER_UNICODE_RANGE.latinExt }, + }, + ], + }, + ], + }), ...(process.env.ANALYZE ? [ analyzer({ From 6d6484c7f63515fe7c192ae80a4325d55ea27298 Mon Sep 17 00:00:00 2001 From: Rishi Raj Jain Date: Sun, 12 Jul 2026 16:10:54 +0530 Subject: [PATCH 4/6] Update tanstack-start.d.ts --- src/tanstack-start.d.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tanstack-start.d.ts b/src/tanstack-start.d.ts index 226e31c7d..284bd9ab3 100644 --- a/src/tanstack-start.d.ts +++ b/src/tanstack-start.d.ts @@ -1,5 +1,6 @@ /// /// +/// // This file ensures TanStack Start type augmentations are loaded // The reference above should trigger the serverRoute module augmentation From 3076a78ec4c6ee43a29f421b4902bdac2222939c Mon Sep 17 00:00:00 2001 From: Rishi Raj Jain Date: Sun, 12 Jul 2026 16:11:06 +0530 Subject: [PATCH 5/6] Update __root.tsx --- src/routes/__root.tsx | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 936d3bf33..c504c9530 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -11,6 +11,7 @@ import { QueryClient } from '@tanstack/react-query' import { createThemeCss, type HighlightTheme } from '@tanstack/highlight/theme' import { auroraXTheme } from '@tanstack/highlight/themes/aurora-x' import { githubLightTheme } from '@tanstack/highlight/themes/github-light' +import { css as fontCss, preloads as fontPreloads } from 'virtual:vite-font' import '~/styles/app.css' import { canonicalUrl, @@ -180,13 +181,15 @@ export const Route = createRootRouteWithContext<{ ], links: [ ...canonicalHeadTags.links, - { - rel: 'preload', - href: '/fonts/Inter-latin.woff2', - as: 'font', - type: 'font/woff2', - crossOrigin: 'anonymous', - }, + ...fontPreloads.map( + (font): React.LinkHTMLAttributes => ({ + rel: 'preload', + href: font.href, + as: 'font', + type: font.type, + crossOrigin: 'anonymous', + }), + ), { rel: 'apple-touch-icon', sizes: '180x180', @@ -207,6 +210,10 @@ export const Route = createRootRouteWithContext<{ { rel: 'manifest', href: '/site.webmanifest', color: '#fffff' }, { rel: 'icon', href: '/favicon.ico' }, ], + // The @font-face rules plus the metric-matched fallback, inlined so the + // first byte of HTML already carries them — no stylesheet round-trip + // before the browser can start the font. + styles: [{ children: fontCss }], scripts: [{ children: GOOGLE_ANALYTICS_BOOTSTRAP }], } }, From f2a67eeaf7406285b30275a889ef378f65e93aa3 Mon Sep 17 00:00:00 2001 From: Rishi Raj Jain Date: Sun, 12 Jul 2026 16:11:14 +0530 Subject: [PATCH 6/6] Update app.css --- src/styles/app.css | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/src/styles/app.css b/src/styles/app.css index fcbc18a06..44645221f 100644 --- a/src/styles/app.css +++ b/src/styles/app.css @@ -21,9 +21,12 @@ html.theme-switching *::after { --breakpoint-xs: 480px; /* Fonts */ + /* 'Inter Fallback' is generated by vite-font (INTER_FALLBACK_FAMILY in + vite.config.ts) and must be spelled identically here, or the metric + overrides that hold the layout steady never get applied. */ --font-sans: - 'Inter', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', - 'Segoe UI Emoji', Segoe UI Symbol, 'Noto Color Emoji'; + 'Inter', 'Inter Fallback', ui-sans-serif, system-ui, sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji', Segoe UI Symbol, 'Noto Color Emoji'; --font-shop-display: 'DM Sans', ui-sans-serif, system-ui, sans-serif; --font-shop-mono: 'DM Mono', ui-monospace, SFMono-Regular, Menlo, monospace; @@ -1090,30 +1093,6 @@ mark { @apply fill-gray-800/90 stroke-gray-500/20; } -@font-face { - font-display: swap; - font-family: 'Inter'; - font-style: normal; - font-weight: 100 900; - src: url('/fonts/Inter-latin-ext.woff2') format('woff2'); - unicode-range: - U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, - U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, - U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; -} - -@font-face { - font-display: swap; - font-family: 'Inter'; - font-style: normal; - font-weight: 100 900; - src: url('/fonts/Inter-latin.woff2') format('woff2'); - unicode-range: - U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, - U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, - U+2215, U+FEFF, U+FFFD; -} - @keyframes fadeIn { from { opacity: 0;