DavidHDev/react-bits · warning · Error
No @font-face rule found in the stylesheet
Error message
No @font-face rule found in the stylesheet
What it means
After successfully fetching the stylesheet CSS, loadFontFromStylesheet regex-matches /@font-face\s*{[^}]*}/g. If no blocks match (or none yield both family and url), family stays null and it throws. This means the fetched body was not valid @font-face CSS — e.g. an HTML intercept/captcha page returned with a 200 status, an empty body, or a format the regex cannot parse.
Source
Thrown at src/ts-tailwind/Components/CircularGallery/CircularGallery.tsx:59
const cssText = await response.text();
const faceBlocks = cssText.match(/@font-face\s*{[^}]*}/g) || [];
let family: string | null = null;
const fontFaces: FontFace[] = [];
for (const block of faceBlocks) {
const familyMatch = block.match(/font-family:\s*['"]?([^;'"]+)['"]?/);
const urlMatch = block.match(/url\(\s*['"]?([^'")]+)['"]?\s*\)/);
if (!familyMatch || !urlMatch) continue;
family = familyMatch[1].trim();
const descriptors: FontFaceDescriptors = {};
const weightMatch = block.match(/font-weight:\s*([^;]+);/);
const styleMatch = block.match(/font-style:\s*([^;]+);/);
const rangeMatch = block.match(/unicode-range:\s*([^;]+);/);
if (weightMatch) descriptors.weight = weightMatch[1].trim();
if (styleMatch) descriptors.style = styleMatch[1].trim();
if (rangeMatch) descriptors.unicodeRange = rangeMatch[1].trim();
fontFaces.push(new FontFace(family, `url(${urlMatch[1]})`, descriptors));
}
if (!family) throw new Error('No @font-face rule found in the stylesheet');
await Promise.allSettled(
fontFaces.map(async face => {
await face.load();
document.fonts.add(face);
})
);
return family;
}
async function loadFontFromFile(url: string): Promise<string> {
const family = deriveFontFamilyFromUrl(url);
const fontFace = new FontFace(family, `url(${url})`);
await fontFace.load();
document.fonts.add(fontFace);
return family;
}
async function loadCustomFont(fontUrl: string): Promise<string> {View on GitHub (pinned to c7109dccb4)
Solutions
- Inspect the actual fetched body: log response.text() to confirm it is CSS with @font-face and not an HTML intercept page.
- Use a direct font-file URL (.woff2/.ttf) via fontUrl so loadFontFromFile runs instead of stylesheet parsing.
- Self-host the font CSS so you control its format and availability.
- Confirm the response Content-Type is text/css and status handling isn't masked by a proxy returning 200 + HTML.
Example fix
// before
const faceBlocks = cssText.match(/@font-face\s*{[^}]*}/g) || [];
// after (diagnose + prefer direct file)
console.debug('font css body:', cssText.slice(0, 200));
// pass a direct file instead of stylesheet:
<CircularGallery fontUrl='/fonts/Figtree.woff2' /> Defensive patterns
Strategy: validation
Validate before calling
function looksLikeFontStylesheet(css: string): boolean {
return /@font-face\s*{/i.test(css) && /url\(/i.test(css);
}
// usage: only call loadFontFromStylesheet when looksLikeFontStylesheet(cssText) is true Try / catch
try {
family = await loadFontFromStylesheet(url);
} catch (e) {
if (e instanceof Error && /No @font-face rule/.test(e.message)) {
family = await loadFontFromFile(url); // try direct file path instead
} else throw e;
} Prevention
- Inspect the fetched CSS body before parsing (log response.text()).
- Prefer direct font-file URLs (.woff2) over stylesheets via fontUrl.
- Self-host the font CSS so you control its format and availability.
- Confirm the response is text/css and not an HTML intercept page.
When it happens
Trigger: cssText (line 41) contains zero parseable @font-face blocks with both font-family and url() at src/ts-tailwind/Components/CircularGallery/CircularGallery.tsx:42-59, so the family variable is never assigned.
Common situations: Captive portal / proxy returning HTML with HTTP 200; Google Fonts returning a browser-conditional CSS that differs from expected (rare); a user-supplied fontUrl pointing to an HTML page or JSON; response.text() truncated by an interceptor; minified/alternative CSS syntax the simple regex misses (e.g. src: with multiple url() and format() tokens — though the regex still finds the first url()).
Related errors
AI-assisted analysis of DavidHDev/react-bits@c7109dccb4 (2026-08-13).
Data as JSON: /api/errors/7444bfe932755fec.
Report an issue: GitHub.