diff --git a/.changeset/fix-font-rendering-with-nextfont.md b/.changeset/fix-font-rendering-with-nextfont.md new file mode 100644 index 00000000..81af07a8 --- /dev/null +++ b/.changeset/fix-font-rendering-with-nextfont.md @@ -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 diff --git a/packages/app/pages/_app.tsx b/packages/app/pages/_app.tsx index e8da0f97..1a1c162e 100644 --- a/packages/app/pages/_app.tsx +++ b/packages/app/pages/_app.tsx @@ -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) { {getLayout()} diff --git a/packages/app/pages/_document.tsx b/packages/app/pages/_document.tsx index 582e5539..bbcab13a 100644 --- a/packages/app/pages/_document.tsx +++ b/packages/app/pages/_document.tsx @@ -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 ( - + {/* eslint-disable-next-line @next/next/no-sync-scripts */}