Self-host Inter and DM Mono fonts (#22506)

Replaces the two Google Fonts stylesheets with self-hosted fonts via
`@fontsource`, imported in the app entry (and Storybook preview), and
removes the duplicate Inter that BlockNote was shipping.

## Why

- **The caching argument for Google Fonts is dead.** Browsers partition
the HTTP cache by top-level site (Chrome 86+, Firefox 85+, Safari even
earlier), so a font cached from another website is never reused on ours.
Every first-time visitor downloads the fonts either way — Google just
adds a detour.
- **Faster first paint.** This removes two render-blocking cross-origin
stylesheets from `index.html` (DNS + TLS to `fonts.googleapis.com`, then
a second connection to `fonts.gstatic.com`, with no preconnect today).
The fonts now ship from our own `/assets` alongside the rest of the app,
behind the same CDN and cache policy.
- **Privacy.** Visitor IPs are no longer sent to Google on every page
load. A German court ruled in 2022 that Google Fonts embedding violates
GDPR, and privacy-conscious self-hosters currently have no way to opt
out of the dependency.
- **Air-gapped / offline self-hosted instances** currently render
fallback system fonts; they now get the real ones.

## Font unification

We were actually loading Inter from two places: the Google stylesheet,
plus `@blocknote/core/fonts/inter.css` (8 weights, latin-only,
woff+woff2) imported by the two rich-text editors — whichever loaded
last won the cascade. Both are gone; `@fontsource` is now the single
source:

- Inter 400/500/600 (theme weights) + 700 (rich-text bold, previously
only covered by the BlockNote copy)
- DM Mono 400/500 (DM Mono has no 600 upstream; the old Google link
requested one anyway)

Fontsource ships the same `unicode-range` subsets as the Google CSS, so
browsers still only download the subset they need (~60KB of woff2 for
latin), and non-latin locales keep full coverage — which the latin-only
BlockNote copy didn't provide.

## Notes

- The BlockNote PDF export (`exportBlockNoteEditorToPdf.ts`) still
fetches Inter TTFs from `fonts.gstatic.com` at export time — react-pdf
needs TTF files, left unchanged here.


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22506?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Félix Malfait
2026-07-03 10:12:27 +02:00
committed by GitHub
parent 13be2188cc
commit 3aeb2b0d5d
8 changed files with 31 additions and 16 deletions
@@ -1,8 +1,3 @@
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
rel="stylesheet"
/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.7/iframeResizer.contentWindow.min.js"></script>
<style type="text/css">
@@ -1,3 +1,10 @@
import '@fontsource/dm-mono/400.css';
import '@fontsource/dm-mono/500.css';
import '@fontsource/inter/400.css';
import '@fontsource/inter/500.css';
import '@fontsource/inter/600.css';
import '@fontsource/inter/700.css';
import { i18n } from '@lingui/core';
import { I18nProvider } from '@lingui/react';
import { type Preview } from '@storybook/react-vite';
-9
View File
@@ -28,15 +28,6 @@
<meta name="twitter:description" content="A modern open-source CRM" />
<meta name="twitter:title" content="Twenty" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
/>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500;600&display=swap"
/>
<title>Twenty</title>
<!-- BEGIN: Twenty Config -->
<script id="twenty-env-config">
+2
View File
@@ -40,6 +40,8 @@
"@dnd-kit/react": "^0.3.2",
"@dr.pogodin/react-helmet": "^3.2.2",
"@floating-ui/react": "^0.24.3",
"@fontsource/dm-mono": "^5.2.7",
"@fontsource/inter": "^5.2.8",
"@graphiql/plugin-explorer": "^5.1.2",
"@graphiql/react": "^0.37.6",
"@hello-pangea/dnd": "^18.0.1",
+6
View File
@@ -3,6 +3,12 @@ import ReactDOM from 'react-dom/client';
import { App } from '@/app/components/App';
import { migrateTokenPairCookieToLocalStorage } from '@/auth/utils/migrateTokenPairCookieToLocalStorage';
import { hydrateMetadataStore } from '@/metadata-store/storage/metadataStoreStorage';
import '@fontsource/dm-mono/400.css';
import '@fontsource/dm-mono/500.css';
import '@fontsource/inter/400.css';
import '@fontsource/inter/500.css';
import '@fontsource/inter/600.css';
import '@fontsource/inter/700.css';
import 'react-loading-skeleton/dist/skeleton.css';
import 'twenty-ui/style.css';
import 'twenty-ui/theme-light.css';
@@ -24,7 +24,6 @@ import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { t } from '@lingui/core/macro';
import '@blocknote/core/fonts/inter.css';
import '@blocknote/mantine/style.css';
import { useCreateBlockNote } from '@blocknote/react';
import '@blocknote/react/style.css';
@@ -17,7 +17,6 @@ import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePush
import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById';
import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType';
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
import '@blocknote/core/fonts/inter.css';
import '@blocknote/mantine/style.css';
import { useCreateBlockNote } from '@blocknote/react';
import '@blocknote/react/style.css';
+16
View File
@@ -7005,6 +7005,20 @@ __metadata:
languageName: node
linkType: hard
"@fontsource/dm-mono@npm:^5.2.7":
version: 5.2.7
resolution: "@fontsource/dm-mono@npm:5.2.7"
checksum: 10c0/028843c26545e86ad01219786daabe18386ed5e19ebefd6bc00fd799f8dc33df0c3d679fe942bf9355696d21903459282d97cd671ee8b2e05cd876560f9bb441
languageName: node
linkType: hard
"@fontsource/inter@npm:^5.2.8":
version: 5.2.8
resolution: "@fontsource/inter@npm:5.2.8"
checksum: 10c0/f737dd50005e4809887ba55ae0c9b7174216d6d14875d17a4fbb9a0ad75dec4265928b805a43fe16a23f14a878f1974a398bbfc84ad65c79fc4d4b9c3ea154e1
languageName: node
linkType: hard
"@gar/promise-retry@npm:^1.0.0, @gar/promise-retry@npm:^1.0.2":
version: 1.0.3
resolution: "@gar/promise-retry@npm:1.0.3"
@@ -52817,6 +52831,8 @@ __metadata:
"@dnd-kit/react": "npm:^0.3.2"
"@dr.pogodin/react-helmet": "npm:^3.2.2"
"@floating-ui/react": "npm:^0.24.3"
"@fontsource/dm-mono": "npm:^5.2.7"
"@fontsource/inter": "npm:^5.2.8"
"@graphiql/plugin-explorer": "npm:^5.1.2"
"@graphiql/react": "npm:^0.37.6"
"@graphql-codegen/cli": "npm:^6.3.1"