// Server-side font loading component // Following Google's best practices: https://web.dev/articles/font-best-practices // - Preconnect to font origins for early connection // - Use font-display: swap (shows fallback immediately, swaps when ready) // - Don't block rendering - some FOUT is acceptable and better UX than invisible text import { getFontConfig, generateFontFaceCSS, getFontPreloadLinks, getGoogleFontsUrl, getFontFamilyValue, getFontBaseSize, } from "src/fonts"; type FontLoaderProps = { headingFontId: string | undefined; bodyFontId: string | undefined; }; export function FontLoader({ headingFontId, bodyFontId }: FontLoaderProps) { const headingFont = getFontConfig(headingFontId); const bodyFont = getFontConfig(bodyFontId); // Collect all unique fonts to load const fontsToLoad = headingFont.id === bodyFont.id ? [headingFont] : [headingFont, bodyFont]; // Collect preload links (deduplicated) const preloadLinksSet = new Set(); const preloadLinks: { href: string; type: string }[] = []; for (const font of fontsToLoad) { for (const link of getFontPreloadLinks(font)) { if (!preloadLinksSet.has(link.href)) { preloadLinksSet.add(link.href); preloadLinks.push(link); } } } // Collect font-face CSS const fontFaceCSS = fontsToLoad .map((font) => generateFontFaceCSS(font)) .filter(Boolean) .join("\n\n"); // Collect Google Fonts URLs (deduplicated) const googleFontsUrls = [...new Set( fontsToLoad .map((font) => getGoogleFontsUrl(font)) .filter((url): url is string => url !== null) )]; const headingFontValue = getFontFamilyValue(headingFont); const bodyFontValue = getFontFamilyValue(bodyFont); const bodyFontBaseSize = getFontBaseSize(bodyFont); // Set font CSS variables scoped to .leafletWrapper so they don't affect app UI const fontVariableCSS = ` .leafletWrapper { --theme-heading-font: ${headingFontValue}; --theme-font: ${bodyFontValue}; --theme-font-base-size: ${bodyFontBaseSize}px; } `.trim(); return ( <> {/* Google Fonts best practice: preconnect to both origins - fonts.googleapis.com serves the CSS - fonts.gstatic.com serves the font files (needs crossorigin for CORS) Place these as early as possible in */} {googleFontsUrls.length > 0 && ( <> {googleFontsUrls.map((url) => ( ))} )} {/* Preload local font files for early discovery */} {preloadLinks.map((link) => ( ))} {/* @font-face declarations (for local fonts) and CSS variable */}