Skip to content

Commit 630592d

Browse files
Fix font rendering issue (#1448)
Co-authored-by: Brandon Pereira <brandon-pereira@users.noreply.github.com>
1 parent 7c391df commit 630592d

File tree

10 files changed

+163
-37
lines changed

10 files changed

+163
-37
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@hyperdx/app': minor
3+
---
4+
5+
# Font Rendering Fix
6+
7+
Migrate from Google Fonts CDN to Next.js self-hosted fonts for improved reliability and production deployment.
8+
9+
## Changes
10+
11+
- Replaced Google Fonts imports with `next/font/google` for IBM Plex Mono, Roboto Mono, Inter, and Roboto
12+
- Font variables are applied server-side in `_document.tsx` and available globally via CSS class inheritance
13+
- Implemented dynamic font switching with CSS variables (`--app-font-family`) and Mantine theme integration
14+
- Font configuration centralized in `src/config/fonts.ts` with derived maps for CSS variables and Mantine compatibility
15+
- Added Roboto font option alongside existing fonts (IBM Plex Mono, Roboto Mono, Inter)
16+
- CSS variable always has a value (defaults to Inter) even when user preference is undefined
17+
- Removed old Google Fonts CDN links from `_document.tsx`
18+
- `!important` flag used only in CSS for external components (nextra sidebar), not in inline styles
19+
- Fonts are now available globally without external CDN dependency, fixing production deployment issues
20+
21+
## Benefits
22+
23+
- ✅ Self-hosted fonts that work in production even when CDNs are blocked
24+
- ✅ Improved performance with automatic optimization
25+
- ✅ Works with Content Security Policy (CSP) headers
26+
- ✅ Mantine components and sidebar now properly inherit selected fonts
27+
- ✅ Font selection persists through user preferences
28+
- ✅ DRY font configuration with derived maps prevents duplication
29+
- ✅ Server-side font setup eliminates runtime performance overhead

packages/app/pages/_app.tsx

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ import {
1717
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
1818

1919
import { IS_LOCAL_MODE } from '@/config';
20+
import {
21+
DEFAULT_FONT_VAR,
22+
DEFAULT_MANTINE_FONT,
23+
FONT_VAR_MAP,
24+
MANTINE_FONT_MAP,
25+
} from '@/config/fonts';
26+
import { ibmPlexMono, inter, roboto, robotoMono } from '@/fonts';
2027
import { ThemeWrapper } from '@/ThemeWrapper';
2128
import { useConfirmModal } from '@/useConfirm';
2229
import { QueryParamProvider as HDXQueryParamProvider } from '@/useQueryParam';
@@ -61,6 +68,9 @@ export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
6168
const confirmModal = useConfirmModal();
6269
const background = useBackground(userPreferences);
6370

71+
const selectedMantineFont =
72+
MANTINE_FONT_MAP[userPreferences.font] || DEFAULT_MANTINE_FONT;
73+
6474
// port to react query ? (needs to wrap with QueryClientProvider)
6575
useEffect(() => {
6676
if (IS_LOCAL_MODE) {
@@ -96,10 +106,27 @@ export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
96106
}, []);
97107

98108
useEffect(() => {
99-
// TODO: Remove after migration to Mantine
100-
document.body.style.fontFamily = userPreferences.font
101-
? `"${userPreferences.font}", sans-serif`
102-
: '"IBM Plex Mono"';
109+
// Apply font classes to html element for CSS variable resolution.
110+
// Although _document.tsx sets these server-side, they must be re-applied client-side
111+
// during hydration to ensure CSS variables are available for dynamic font switching.
112+
// This is critical for the --app-font-family CSS variable to work across all components.
113+
if (typeof document !== 'undefined') {
114+
const fontClasses = [
115+
ibmPlexMono.variable,
116+
robotoMono.variable,
117+
inter.variable,
118+
roboto.variable,
119+
];
120+
document.documentElement.classList.add(...fontClasses);
121+
}
122+
}, []);
123+
124+
useEffect(() => {
125+
// Update CSS variable for global font cascading
126+
if (typeof document !== 'undefined') {
127+
const fontVar = FONT_VAR_MAP[userPreferences.font] || DEFAULT_FONT_VAR;
128+
document.documentElement.style.setProperty('--app-font-family', fontVar);
129+
}
103130
}, [userPreferences.font]);
104131

105132
const getLayout = Component.getLayout ?? (page => page);
@@ -124,7 +151,7 @@ export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
124151
<QueryParamProvider adapter={NextAdapter}>
125152
<QueryClientProvider client={queryClient}>
126153
<ThemeWrapper
127-
fontFamily={userPreferences.font}
154+
fontFamily={selectedMantineFont}
128155
colorScheme={userPreferences.theme === 'dark' ? 'dark' : 'light'}
129156
>
130157
{getLayout(<Component {...pageProps} />)}

packages/app/pages/_document.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import { Head, Html, Main, NextScript } from 'next/document';
22

3+
import { ibmPlexMono, inter, roboto, robotoMono } from '@/fonts';
4+
35
export default function Document() {
6+
const fontClasses = [
7+
ibmPlexMono.variable,
8+
robotoMono.variable,
9+
inter.variable,
10+
roboto.variable,
11+
].join(' ');
12+
413
return (
5-
<Html lang="en">
14+
<Html lang="en" className={fontClasses}>
615
<Head>
716
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
817
<script src="/__ENV.js" />
@@ -12,16 +21,6 @@ export default function Document() {
1221
rel="stylesheet"
1322
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css"
1423
/>
15-
<link rel="preconnect" href="https://fonts.googleapis.com" />
16-
<link
17-
rel="preconnect"
18-
href="https://fonts.gstatic.com"
19-
crossOrigin="anonymous"
20-
/>
21-
<link
22-
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;600;700&display=swap"
23-
rel="stylesheet"
24-
/>
2524
</Head>
2625
<body>
2726
<Main />

packages/app/src/AppNav.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ export default function AppNav({ fixed = false }: { fixed?: boolean }) {
604604
onHide={closeInstallInstructions}
605605
/>
606606
<div
607-
className={`${styles.wrapper} inter`}
607+
className={`${styles.wrapper}`}
608608
style={{
609609
position: fixed ? 'fixed' : 'initial',
610610
letterSpacing: '0.05em',

packages/app/src/UserPreferencesModal.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,9 @@ import {
1414
Text,
1515
} from '@mantine/core';
1616

17+
import { OPTIONS_FONTS } from './config/fonts';
1718
import { UserPreferences, useUserPreferences } from './useUserPreferences';
1819

19-
const OPTIONS_FONTS = [
20-
'IBM Plex Mono',
21-
'Roboto Mono',
22-
'Inter',
23-
{ value: 'or use your own font', disabled: true },
24-
];
25-
2620
const OPTIONS_THEMES = [
2721
{ label: 'Dark', value: 'dark' },
2822
{ label: 'Light', value: 'light' },

packages/app/src/config/fonts.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
export type FontConfig = {
2+
variable: string;
3+
fallback: string;
4+
};
5+
6+
export const FONT_CONFIG: Record<string, FontConfig> = {
7+
'IBM Plex Mono': {
8+
variable: 'var(--font-ibm-plex-mono)',
9+
fallback: 'monospace',
10+
},
11+
'Roboto Mono': {
12+
variable: 'var(--font-roboto-mono)',
13+
fallback: 'monospace',
14+
},
15+
Inter: {
16+
variable: 'var(--font-inter)',
17+
fallback: 'sans-serif',
18+
},
19+
Roboto: {
20+
variable: 'var(--font-roboto)',
21+
fallback: 'sans-serif',
22+
},
23+
};
24+
25+
export const DEFAULT_FONT_CONFIG = FONT_CONFIG.Inter;
26+
27+
// Derived maps for convenience
28+
export const FONT_VAR_MAP = Object.entries(FONT_CONFIG).reduce(
29+
(acc, [name, config]) => {
30+
acc[name] = config.variable;
31+
return acc;
32+
},
33+
{} as Record<string, string>,
34+
);
35+
36+
export const MANTINE_FONT_MAP = Object.entries(FONT_CONFIG).reduce(
37+
(acc, [name, config]) => {
38+
acc[name] = `${config.variable}, ${config.fallback}`;
39+
return acc;
40+
},
41+
{} as Record<string, string>,
42+
);
43+
44+
export const DEFAULT_FONT_VAR = DEFAULT_FONT_CONFIG.variable;
45+
export const DEFAULT_MANTINE_FONT = `${DEFAULT_FONT_CONFIG.variable}, ${DEFAULT_FONT_CONFIG.fallback}`;
46+
47+
// UI options for font selection
48+
export const OPTIONS_FONTS = [
49+
'IBM Plex Mono',
50+
'Roboto Mono',
51+
'Inter',
52+
'Roboto',
53+
];

packages/app/src/fonts.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { IBM_Plex_Mono, Inter, Roboto, Roboto_Mono } from 'next/font/google';
2+
3+
export const ibmPlexMono = IBM_Plex_Mono({
4+
weight: ['300', '400', '500', '600', '700'],
5+
subsets: ['latin'],
6+
variable: '--font-ibm-plex-mono',
7+
display: 'swap',
8+
});
9+
10+
export const robotoMono = Roboto_Mono({
11+
weight: ['100', '300', '400', '500', '700'],
12+
subsets: ['latin'],
13+
variable: '--font-roboto-mono',
14+
display: 'swap',
15+
});
16+
17+
export const inter = Inter({
18+
weight: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
19+
subsets: ['latin'],
20+
variable: '--font-inter',
21+
display: 'swap',
22+
});
23+
24+
export const roboto = Roboto({
25+
weight: ['100', '300', '400', '500', '700'],
26+
subsets: ['latin'],
27+
variable: '--font-roboto',
28+
display: 'swap',
29+
});

packages/app/src/useUserPreferences.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export type UserPreferences = {
77
isUTC: boolean;
88
timeFormat: '12h' | '24h';
99
theme: 'light' | 'dark';
10-
font: 'IBM Plex Mono' | 'Inter';
10+
font: 'IBM Plex Mono' | 'Roboto Mono' | 'Inter' | 'Roboto';
1111
backgroundEnabled?: boolean;
1212
backgroundUrl?: string;
1313
backgroundBlur?: number;

packages/app/styles/app.scss

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,30 @@
88
@import './_utilities';
99

1010
@import url('https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css');
11-
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;600;700&display=swap');
12-
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400;500;700&display=swap');
13-
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap');
14-
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@100..700&display=swap');
1511

1612
.inter {
17-
font-family: Inter, Roboto, 'Helvetica Neue', sans-serif;
13+
font-family: var(--font-inter), 'Helvetica Neue', sans-serif;
1814
}
1915
.roboto {
20-
font-family: 'Roboto', sans-serif;
16+
font-family: var(--font-roboto), sans-serif;
2117
}
2218
.roboto-mono {
23-
font-family: 'Roboto Mono', monospace;
19+
font-family: var(--font-roboto-mono), monospace;
2420
}
2521
.mono {
26-
font-family: 'IBM Plex Mono', monospace;
22+
font-family: var(--font-ibm-plex-mono), monospace;
2723
}
2824

2925

3026
:root {
3127
--mantine-webkit-font-smoothing: inherit;
3228
--mantine-moz-font-smoothing: inherit;
3329
color-scheme: dark;
30+
--app-font-family: var(--font-inter);
3431
}
3532

3633
body {
37-
font-family: 'IBM Plex Mono', monospace;
34+
font-family: var(--app-font-family);
3835
font-size: var(--mantine-font-size-sm);
3936
}
4037

@@ -200,7 +197,7 @@ body {
200197
// Nextra Overrides
201198
.nextra-toc,
202199
.nextra-sidebar-container {
203-
font-family: 'Roboto', sans-serif;
200+
font-family: var(--font-inter) !important;
204201
}
205202

206203
// custom overrides for jsontree

packages/app/styles/globals.css

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ html,
22
body {
33
padding: 0;
44
margin: 0;
5-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
6-
Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
75
}
86

97
a {

0 commit comments

Comments
 (0)