
本文介绍在 React 应用中基于 .env 文件动态配置并加载多字体的完整方案,涵盖运行时字体注入、环境感知路径拼接、CSS 变量集成及最佳实践,避免硬编码与构建时限制。
本文介绍在 react 应用中基于 `.env` 文件动态配置并加载多字体的完整方案,涵盖运行时字体注入、环境感知路径拼接、css 变量集成及最佳实践,避免硬编码与构建时限制。
在 React 项目中,将字体路径硬编码在 CSS/SCSS 文件中会导致环境切换(如开发、测试、生产)时需手动修改,既不安全也不可维护。由于 CSS 预处理器(如 Sass)无法直接读取 JavaScript 运行时环境变量(如 process.env.REACT_APP_FONT_CDN),因此不能通过 SCSS 全局变量实现真正的“环境驱动字体路径”——这正是你在 font.scss 中尝试失败的根本原因。
✅ 正确思路是:在应用启动或组件挂载时,通过 JavaScript 动态创建 FontFace 实例,并根据 process.env 注入对应环境的字体 URL。该方式完全绕过构建时限制,支持任意数量字体、按需加载、错误处理及样式即时生效。
以下为推荐的工程化实现方案:
1. 配置环境变量(.env 文件)
# .env.development REACT_APP_FONT_CDN=https://cdn-dev.example.com/fonts # .env.production REACT_APP_FONT_CDN=https://cdn-prod.example.com/fonts # .env.staging REACT_APP_FONT_CDN=https://cdn-staging.example.com/fonts
2. 创建可复用的字体加载工具函数
// utils/fontLoader.ts
export interface FontConfig {
family: string;
url: string;
weight?: string;
style?: string;
display?: 'auto' | 'block' | 'swap' | 'fallback' | 'optional';
}
export const loadFonts = async (fonts: FontConfig[]): Promise<void> => {
const fontPromises = fonts.map(({ family, url, weight, style, display }) => {
const fontFace = new FontFace(family, `url(${url})`, {
weight: weight || 'normal',
style: style || 'normal',
display: display || 'swap',
});
return fontFace.load().then((loadedFont) => {
if (!document.fonts.has(loadedFont)) {
document.fonts.add(loadedFont);
}
});
});
await Promise.all(fontPromises);
};3. 在主入口(如 index.tsx)或根组件中初始化
// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { loadFonts } from './utils/fontLoader';
// 定义各环境字体映射(路径自动拼接 CDN 基础地址)
const FONT_CONFIGS: Record<string, FontConfig[]> = {
development: [
{ family: 'Inter', url: `${process.env.REACT_APP_FONT_CDN}/inter-v12-latin-regular.woff2` },
{ family: 'Inter', url: `${process.env.REACT_APP_FONT_CDN}/inter-v12-latin-700.woff2`, weight: '700' },
],
production: [
{ family: 'Inter', url: `${process.env.REACT_APP_FONT_CDN}/inter-v12-latin-regular.woff2` },
{ family: 'Inter', url: `${process.env.REACT_APP_FONT_CDN}/inter-v12-latin-700.woff2`, weight: '700' },
{ family: 'DM Sans', url: `${process.env.REACT_APP_FONT_CDN}/dm-sans-v13-latin-400.woff2` },
],
staging: [
{ family: 'Inter', url: `${process.env.REACT_APP_FONT_CDN}/inter-v12-latin-regular.woff2` },
],
};
const env = process.env.NODE_ENV as keyof typeof FONT_CONFIGS;
const fontsToLoad = FONT_CONFIGS[env] || FONT_CONFIGS.development;
// 启动前预加载字体(可加 loading 状态或兜底字体)
loadFonts(fontsToLoad).catch(console.error);
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);4. 在 CSS 中声明字体族(无需路径,仅声明 family)
/* src/index.css */
@font-face {
font-family: 'Inter';
font-weight: 400;
font-style: normal;
/* 注意:此处不写 src,由 JS 动态注入,避免重复加载 */
}
@font-face {
font-family: 'Inter';
font-weight: 700;
font-style: normal;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
}⚠️ 关键注意事项
- ✅
FontFace.load()是异步的,务必await或.then()处理,否则document.fonts.add()可能失败; - ✅ 使用
display: 'swap'可提升首屏体验(文本先显示后备字体,加载完成立即替换); - ❌ 不要在
useEffect中重复调用loadFonts(字体只需加载一次); - ✅ 支持 WOFF2 / WOFF / TTF,但需确保服务端正确设置
Content-Type: font/woff2; - ? 生产环境建议对字体文件开启 HTTP 缓存(
Cache-Control: public, max-age=31536000); - ? 如需主题化字体(如深色模式切换字体族),可结合
useContext+useState封装字体切换 Hook。
通过该方案,你彻底解耦了字体路径与构建流程,实现了真正意义上的「环境驱动、动态加载、多字体支持」,兼具健壮性与可维护性。


















