mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
Fix font rendering issue (#1448)
Co-authored-by: Brandon Pereira <brandon-pereira@users.noreply.github.com>
This commit is contained in:
parent
7c391dfb02
commit
630592dbff
10 changed files with 163 additions and 37 deletions
29
.changeset/fix-font-rendering-with-nextfont.md
Normal file
29
.changeset/fix-font-rendering-with-nextfont.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
'@hyperdx/app': minor
|
||||
---
|
||||
|
||||
# Font Rendering Fix
|
||||
|
||||
Migrate from Google Fonts CDN to Next.js self-hosted fonts for improved reliability and production deployment.
|
||||
|
||||
## Changes
|
||||
|
||||
- Replaced Google Fonts imports with `next/font/google` for IBM Plex Mono, Roboto Mono, Inter, and Roboto
|
||||
- Font variables are applied server-side in `_document.tsx` and available globally via CSS class inheritance
|
||||
- Implemented dynamic font switching with CSS variables (`--app-font-family`) and Mantine theme integration
|
||||
- Font configuration centralized in `src/config/fonts.ts` with derived maps for CSS variables and Mantine compatibility
|
||||
- Added Roboto font option alongside existing fonts (IBM Plex Mono, Roboto Mono, Inter)
|
||||
- CSS variable always has a value (defaults to Inter) even when user preference is undefined
|
||||
- Removed old Google Fonts CDN links from `_document.tsx`
|
||||
- `!important` flag used only in CSS for external components (nextra sidebar), not in inline styles
|
||||
- Fonts are now available globally without external CDN dependency, fixing production deployment issues
|
||||
|
||||
## Benefits
|
||||
|
||||
- ✅ Self-hosted fonts that work in production even when CDNs are blocked
|
||||
- ✅ Improved performance with automatic optimization
|
||||
- ✅ Works with Content Security Policy (CSP) headers
|
||||
- ✅ Mantine components and sidebar now properly inherit selected fonts
|
||||
- ✅ Font selection persists through user preferences
|
||||
- ✅ DRY font configuration with derived maps prevents duplication
|
||||
- ✅ Server-side font setup eliminates runtime performance overhead
|
||||
|
|
@ -17,6 +17,13 @@ import {
|
|||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
|
||||
import { IS_LOCAL_MODE } from '@/config';
|
||||
import {
|
||||
DEFAULT_FONT_VAR,
|
||||
DEFAULT_MANTINE_FONT,
|
||||
FONT_VAR_MAP,
|
||||
MANTINE_FONT_MAP,
|
||||
} from '@/config/fonts';
|
||||
import { ibmPlexMono, inter, roboto, robotoMono } from '@/fonts';
|
||||
import { ThemeWrapper } from '@/ThemeWrapper';
|
||||
import { useConfirmModal } from '@/useConfirm';
|
||||
import { QueryParamProvider as HDXQueryParamProvider } from '@/useQueryParam';
|
||||
|
|
@ -61,6 +68,9 @@ export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
|||
const confirmModal = useConfirmModal();
|
||||
const background = useBackground(userPreferences);
|
||||
|
||||
const selectedMantineFont =
|
||||
MANTINE_FONT_MAP[userPreferences.font] || DEFAULT_MANTINE_FONT;
|
||||
|
||||
// port to react query ? (needs to wrap with QueryClientProvider)
|
||||
useEffect(() => {
|
||||
if (IS_LOCAL_MODE) {
|
||||
|
|
@ -96,10 +106,27 @@ export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: Remove after migration to Mantine
|
||||
document.body.style.fontFamily = userPreferences.font
|
||||
? `"${userPreferences.font}", sans-serif`
|
||||
: '"IBM Plex Mono"';
|
||||
// Apply font classes to html element for CSS variable resolution.
|
||||
// Although _document.tsx sets these server-side, they must be re-applied client-side
|
||||
// during hydration to ensure CSS variables are available for dynamic font switching.
|
||||
// This is critical for the --app-font-family CSS variable to work across all components.
|
||||
if (typeof document !== 'undefined') {
|
||||
const fontClasses = [
|
||||
ibmPlexMono.variable,
|
||||
robotoMono.variable,
|
||||
inter.variable,
|
||||
roboto.variable,
|
||||
];
|
||||
document.documentElement.classList.add(...fontClasses);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Update CSS variable for global font cascading
|
||||
if (typeof document !== 'undefined') {
|
||||
const fontVar = FONT_VAR_MAP[userPreferences.font] || DEFAULT_FONT_VAR;
|
||||
document.documentElement.style.setProperty('--app-font-family', fontVar);
|
||||
}
|
||||
}, [userPreferences.font]);
|
||||
|
||||
const getLayout = Component.getLayout ?? (page => page);
|
||||
|
|
@ -124,7 +151,7 @@ export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
|||
<QueryParamProvider adapter={NextAdapter}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ThemeWrapper
|
||||
fontFamily={userPreferences.font}
|
||||
fontFamily={selectedMantineFont}
|
||||
colorScheme={userPreferences.theme === 'dark' ? 'dark' : 'light'}
|
||||
>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,17 @@
|
|||
import { Head, Html, Main, NextScript } from 'next/document';
|
||||
|
||||
import { ibmPlexMono, inter, roboto, robotoMono } from '@/fonts';
|
||||
|
||||
export default function Document() {
|
||||
const fontClasses = [
|
||||
ibmPlexMono.variable,
|
||||
robotoMono.variable,
|
||||
inter.variable,
|
||||
roboto.variable,
|
||||
].join(' ');
|
||||
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Html lang="en" className={fontClasses}>
|
||||
<Head>
|
||||
{/* eslint-disable-next-line @next/next/no-sync-scripts */}
|
||||
<script src="/__ENV.js" />
|
||||
|
|
@ -12,16 +21,6 @@ export default function Document() {
|
|||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css"
|
||||
/>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link
|
||||
rel="preconnect"
|
||||
href="https://fonts.gstatic.com"
|
||||
crossOrigin="anonymous"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
|
|
|
|||
|
|
@ -604,7 +604,7 @@ export default function AppNav({ fixed = false }: { fixed?: boolean }) {
|
|||
onHide={closeInstallInstructions}
|
||||
/>
|
||||
<div
|
||||
className={`${styles.wrapper} inter`}
|
||||
className={`${styles.wrapper}`}
|
||||
style={{
|
||||
position: fixed ? 'fixed' : 'initial',
|
||||
letterSpacing: '0.05em',
|
||||
|
|
|
|||
|
|
@ -14,15 +14,9 @@ import {
|
|||
Text,
|
||||
} from '@mantine/core';
|
||||
|
||||
import { OPTIONS_FONTS } from './config/fonts';
|
||||
import { UserPreferences, useUserPreferences } from './useUserPreferences';
|
||||
|
||||
const OPTIONS_FONTS = [
|
||||
'IBM Plex Mono',
|
||||
'Roboto Mono',
|
||||
'Inter',
|
||||
{ value: 'or use your own font', disabled: true },
|
||||
];
|
||||
|
||||
const OPTIONS_THEMES = [
|
||||
{ label: 'Dark', value: 'dark' },
|
||||
{ label: 'Light', value: 'light' },
|
||||
|
|
|
|||
53
packages/app/src/config/fonts.ts
Normal file
53
packages/app/src/config/fonts.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
export type FontConfig = {
|
||||
variable: string;
|
||||
fallback: string;
|
||||
};
|
||||
|
||||
export const FONT_CONFIG: Record<string, FontConfig> = {
|
||||
'IBM Plex Mono': {
|
||||
variable: 'var(--font-ibm-plex-mono)',
|
||||
fallback: 'monospace',
|
||||
},
|
||||
'Roboto Mono': {
|
||||
variable: 'var(--font-roboto-mono)',
|
||||
fallback: 'monospace',
|
||||
},
|
||||
Inter: {
|
||||
variable: 'var(--font-inter)',
|
||||
fallback: 'sans-serif',
|
||||
},
|
||||
Roboto: {
|
||||
variable: 'var(--font-roboto)',
|
||||
fallback: 'sans-serif',
|
||||
},
|
||||
};
|
||||
|
||||
export const DEFAULT_FONT_CONFIG = FONT_CONFIG.Inter;
|
||||
|
||||
// Derived maps for convenience
|
||||
export const FONT_VAR_MAP = Object.entries(FONT_CONFIG).reduce(
|
||||
(acc, [name, config]) => {
|
||||
acc[name] = config.variable;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
);
|
||||
|
||||
export const MANTINE_FONT_MAP = Object.entries(FONT_CONFIG).reduce(
|
||||
(acc, [name, config]) => {
|
||||
acc[name] = `${config.variable}, ${config.fallback}`;
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, string>,
|
||||
);
|
||||
|
||||
export const DEFAULT_FONT_VAR = DEFAULT_FONT_CONFIG.variable;
|
||||
export const DEFAULT_MANTINE_FONT = `${DEFAULT_FONT_CONFIG.variable}, ${DEFAULT_FONT_CONFIG.fallback}`;
|
||||
|
||||
// UI options for font selection
|
||||
export const OPTIONS_FONTS = [
|
||||
'IBM Plex Mono',
|
||||
'Roboto Mono',
|
||||
'Inter',
|
||||
'Roboto',
|
||||
];
|
||||
29
packages/app/src/fonts.ts
Normal file
29
packages/app/src/fonts.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { IBM_Plex_Mono, Inter, Roboto, Roboto_Mono } from 'next/font/google';
|
||||
|
||||
export const ibmPlexMono = IBM_Plex_Mono({
|
||||
weight: ['300', '400', '500', '600', '700'],
|
||||
subsets: ['latin'],
|
||||
variable: '--font-ibm-plex-mono',
|
||||
display: 'swap',
|
||||
});
|
||||
|
||||
export const robotoMono = Roboto_Mono({
|
||||
weight: ['100', '300', '400', '500', '700'],
|
||||
subsets: ['latin'],
|
||||
variable: '--font-roboto-mono',
|
||||
display: 'swap',
|
||||
});
|
||||
|
||||
export const inter = Inter({
|
||||
weight: ['100', '200', '300', '400', '500', '600', '700', '800', '900'],
|
||||
subsets: ['latin'],
|
||||
variable: '--font-inter',
|
||||
display: 'swap',
|
||||
});
|
||||
|
||||
export const roboto = Roboto({
|
||||
weight: ['100', '300', '400', '500', '700'],
|
||||
subsets: ['latin'],
|
||||
variable: '--font-roboto',
|
||||
display: 'swap',
|
||||
});
|
||||
|
|
@ -7,7 +7,7 @@ export type UserPreferences = {
|
|||
isUTC: boolean;
|
||||
timeFormat: '12h' | '24h';
|
||||
theme: 'light' | 'dark';
|
||||
font: 'IBM Plex Mono' | 'Inter';
|
||||
font: 'IBM Plex Mono' | 'Roboto Mono' | 'Inter' | 'Roboto';
|
||||
backgroundEnabled?: boolean;
|
||||
backgroundUrl?: string;
|
||||
backgroundBlur?: number;
|
||||
|
|
|
|||
|
|
@ -8,22 +8,18 @@
|
|||
@import './_utilities';
|
||||
|
||||
@import url('https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@100;300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@100..700&display=swap');
|
||||
|
||||
.inter {
|
||||
font-family: Inter, Roboto, 'Helvetica Neue', sans-serif;
|
||||
font-family: var(--font-inter), 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
.roboto {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-family: var(--font-roboto), sans-serif;
|
||||
}
|
||||
.roboto-mono {
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
font-family: var(--font-roboto-mono), monospace;
|
||||
}
|
||||
.mono {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-family: var(--font-ibm-plex-mono), monospace;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -31,10 +27,11 @@
|
|||
--mantine-webkit-font-smoothing: inherit;
|
||||
--mantine-moz-font-smoothing: inherit;
|
||||
color-scheme: dark;
|
||||
--app-font-family: var(--font-inter);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'IBM Plex Mono', monospace;
|
||||
font-family: var(--app-font-family);
|
||||
font-size: var(--mantine-font-size-sm);
|
||||
}
|
||||
|
||||
|
|
@ -200,7 +197,7 @@ body {
|
|||
// Nextra Overrides
|
||||
.nextra-toc,
|
||||
.nextra-sidebar-container {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-family: var(--font-inter) !important;
|
||||
}
|
||||
|
||||
// custom overrides for jsontree
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@ html,
|
|||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
|
|
|
|||
Loading…
Reference in a new issue