Troubleshooting
Common CLI issues and how to fix them. Each entry follows the same structure: the error you see, why it happens, and steps to resolve it.
Destination directory does not exist
What error appeared
The CLI exits with code 1 and prints a message similar to:
Error: Destination directory "path/to/your/fonts" does not exist. Use --dest-create (-m) to create it.You may also see progress output before the error when --verbose is enabled, for example:
Generating SVG font...
Error: Destination directory "path/to/your/fonts" does not exist. Use --dest-create (-m) to create it.On older versions, the same situation could surface as a raw filesystem error instead:
Error: ENOENT: no such file or directory, open 'path/to/your/fonts/webfont.woff2'In those cases the process could still exit with code 0 even though no font files were written.
Why it usually happens
--dest/-dpoints to a folder that was never created. Webfont does not create the output directory unless you ask it to.- The path is relative to your current working directory. A path that looks correct in your editor may not exist from the shell directory where you run the command.
- A build script or CI job runs before
mkdir. Scripts often assumedist/fontsorpublic/iconsalready exist. - A typo in the path (extra segment, wrong casing on case-sensitive filesystems, or a missing parent directory).
Font generation can finish before files are written. If the destination folder is missing, the write step fails even though earlier steps (and verbose logs) may have already run.
Steps to try to resolve
Confirm the path exists from the same directory where you run webfont.
shellls -la path/to/your/fontsIf the directory is missing, either create it yourself or use step 2.
Ask webfont to create the destination directory.
shellwebfont "src/icons/*.svg" -d path/to/your/fonts --dest-createShort form:
shellwebfont "src/icons/*.svg" -d path/to/your/fonts -mCreate the directory manually (useful in CI or when you want explicit control):
shellmkdir -p path/to/your/fonts webfont "src/icons/*.svg" -d path/to/your/fontsUse an absolute path for
--destif your script changes working directories:shellwebfont "src/icons/*.svg" -d "$(pwd)/dist/fonts"Check write permissions on the parent directory if creation still fails:
shellmkdir -p path/to/your/fonts touch path/to/your/fonts/.write-test && rm path/to/your/fonts/.write-testWhen using the programmatic API, set
destCreate: trueif the folder may not exist yet:jsimport { webfont } from "webfont"; await webfont({ files: "src/icons/*.svg", dest: "dist/fonts", destCreate: true, });Upgrade webfont if you see exit code 0 with no output files and only an
ENOENTmessage. Current releases fail with exit code 1 and the clearer destination error above.
If the problem persists, open an issue with the full command, your working directory, and the complete terminal output.
npm warn Unknown env config "devdir"
What error appeared
Every npm command prints a warning similar to:
npm warn Unknown env config "devdir". This will stop working in the next major version of npm. See `npm help npmrc` for supported config options.The command still succeeds; only the warning is noisy.
Why it usually happens
- Cursor (or some IDE sandboxes) inject
npm_config_devdirinto the shell environment so node-gyp uses a sandbox cache directory. - npm 11.2+ warns because
devdiris not an official npm config key — it is a legacy node-gyp convention. - This repository does not set
devdir. The project.npmrconly containssave-exact=true.
Confirm the source in your terminal:
echo "$npm_config_devdir"If the path contains cursor-sandbox-cache (or similar), the IDE injected it — not webfont.
Steps to try to resolve
Clear the variable for one command:
shellnpm_config_devdir= npm testClear it for the current shell session:
shellunset npm_config_devdirCursor / VS Code integrated terminal — this repo ships
.vscode/settings.jsonwithnpm_config_devdircleared. Open a new terminal or reload the window (Developer: Reload Window) so the setting applies. Existing terminals keep the old environment.CI and plain shells outside Cursor are unaffected; no change is required there.
This warning does not indicate a broken install or a webfont bug.
Config files ignored when using --config
What error appeared
You pass --config webfont.config.json with a files array in the config, but webfont only processes SVG paths from the command line — or shows help when you omit positional arguments.
Example config:
{
"files": ["src/icons/a.svg", "src/icons/b.svg"],
"dest": "dist/icons",
"fontName": "my-icons"
}Older releases (before the fix in #696):
webfont --config webfont.config.json -d dist/icons
# Shows help or does not pick up both files from configIf you also pass SVG paths on the CLI while files is set in the config, older releases may silently prefer CLI input only.
After the fix ships (upgrade required), combining both causes a clear error:
Error: Cannot specify input files on the command line when `files` is set in the config fileWhy it usually happens
- The CLI treated positional arguments as the only input source. Config
fileswas merged insidewebfont(), but the CLI required at least one path on the command line before running. filesin config and CLI input are mutually exclusive by design — use one or the other to avoid ambiguity about which globs win.
Steps to try to resolve
Upgrade webfont to a version that includes the fix for #2 (see release notes / #696). Then run without positional SVG arguments:
shellwebfont --config webfont.config.json -d dist/iconsUntil you upgrade, pass all globs on the command line and use the config only for other options (
fontName,dest,formats, etc.):shellwebfont "src/icons/a.svg" "src/icons/b.svg" --config webfont.config.json -d dist/iconsEnsure the config file does not contain a
fileskey in this mode.Programmatic API — pass
filesdirectly (config discovery still merges other keys):jsimport { webfont } from "webfont"; await webfont({ files: ["src/icons/a.svg", "src/icons/b.svg"], configFile: "webfont.config.json", });Do not mix CLI positional paths with
filesin the config after upgrading — pick one source of truth.
If the problem persists after upgrading, open an issue with your config file, full command, and webfont version (webfont --version or npm ls webfont).
Icon details missing after export
What error appeared
There is often no error. The font builds successfully, but part of the icon is missing in the browser — for example the dot on a letter in a LinkedIn-style logo (#175).
The same SVG looks correct in Inkscape, Illustrator, or Affinity Designer.
Why it usually happens
fill-rule: evenoddon compound paths. Many design tools export icons as one path with multiple subpaths andfill-rule: evenodd(often via an inlinestyleon the root<svg>). Browsers still draw that SVG correctly.- Icon fonts use the nonzero fill rule. When the SVG is converted to a font glyph,
fill-ruleis not preserved. Counter-shapes and holes can disappear or invert — the geometry is often still in the glyph path, but it renders wrong. --normalizedoes not fix fill-rule. Normalization adjusts metrics and path coordinates; it does not change evenodd semantics (#80).- Small viewBoxes can make detail loss worse for complex paths. Try a larger artboard (for example 512×512) and
fontHeight: 600withnormalize: truewhen paths are very small (#80).
Run with --verbose to log a warning when webfont detects fill-rule: evenodd in a source SVG.
Alpha — broader diagnostics: use --svg-diagnose (CLI) or svgTools: { diagnose: true } (API) to also flag stroke-only SVGs, <use> symbol references (#612), and unsupported elements (<line>, <polyline>, <clipPath>). webfont does not auto-fix these — preprocess with glyphContentTransformFn when needed (see ADR 0011).
Steps to try to resolve
Inspect the SVG with nonzero fill rule. Temporarily change
fill-rule:evenoddtofill-rule:nonzeroin the file and reopen it in a vector editor. Missing pieces usually indicate path direction or compound-path issues, not a silent webfont failure.Fix the source in your design tool (recommended).
- Merge shapes with Unite / Union, then Object → Compound Path → Make (Illustrator: Ctrl/Cmd+8) so holes use correct winding (#175).
- Avoid leaving
fill-rule: evenoddon icons destined for icon fonts. - Export at a larger size (for example 512×512) when the tool produces fragile small paths.
Tune font metrics for fine detail:
jsawait webfont({ files: "src/icons/**/*.svg", normalize: true, fontHeight: 600, });Preprocess difficult SVGs with
glyphContentTransformFn(for example retrace or flatten strokes withsvg-outline-strokeor svg-fixer, installed in your project) before generating the font.Remove percentage
width/heighton the root<svg>if a preprocessor errors; keep a numericviewBoxinstead.
See also MDN: fill-rule.
Stroke-only SVGs produce blank icons
What error appeared
On releases after the #327 fix, webfont fails the run with a message similar to:
Empty glyph path(s) in SVG font output for: wave (icons/wave.svg). Stroke-only SVGs (fill="none" with stroke) often produce empty glyphs because svgicons2svgfont does not convert strokes. ...On older releases, the command may exit successfully but the icon is invisible in the browser — the font file exists, yet the glyph path is empty (d="").
Why it usually happens
- The SVG uses strokes instead of filled paths (
fill="none"withstroke, or<line>/<polyline>elements). - svgicons2svgfont (the library that merges SVGs into a font) does not convert strokes to outlines. Stroke geometry is dropped, so the exported glyph has no path data.
- Design tools and browsers still preview the SVG correctly; only the font export is affected.
Steps to try to resolve
Convert strokes to fills in your design tool (Outline Stroke / Expand / Object to Path) before export.
Preprocess in your build with
glyphContentTransformFn. webfont does not ship stroke converters — install a tool in your project (for examplesvg-outline-strokeor svg-fixer) and call it from the hook (see ADR 0011).Scan sources before converting:
webfont icons/*.svg --svg-diagnose(orsvgTools: { diagnose: true }in the API) logs stroke-only and unsupported-element warnings without changing files.Optional SVGO cleanup:
webfont icons/*.svg --optimize-svg(oroptimizeSvg: true) removes comments and editor metadata before conversion — it does not convert strokes. Use beforeglyphContentTransformFnwhen you need both cleanup and stroke outlining (#724).Optimize with SVGO only after strokes are outlines — aggressive SVGO presets alone do not fix stroke-only artwork for icon fonts.
SVG transform and <use> references
What error appeared
There is often no build error. The font generates, but a glyph is empty, wrong, or missing a flip/offset that looks correct in the browser. Source SVGs may look like:
<use transform="matrix(1 0 0 -1 0 21.985684)" xlink:href="#symbol-id"></use>See #612.
Why it usually happens
- svgicons2svgfont (the library that merges SVGs into a font) converts filled paths on each icon file. It does not resolve
<use>/<symbol>references or baketransformon those instances into standalone path data. transformon paths/groups is experimental upstream;<use xlink:href="#…">is a separate limitation — the referenced geometry is never inlined.- Design tools and browsers render
<use>+transformcorrectly; the font pipeline does not.
Steps to try to resolve
Flatten before webfont — in Illustrator, Inkscape, or Figma export: Expand / Flatten / Outline stroke, or export plain SVG without symbols/instances.
SVGO (in your build): plugins such as
convertShapeToPathandcleanupIdshelp; use a preset that inlines or removes<use>where possible. webfont’s--optimize-svgdoes not inline<use>— run SVGO inglyphContentTransformFnif you need that step.Scan sources:
webfont icons/*.svg --svg-diagnosewarns when<use>is detected (12.1.0+).Preprocess in code:
jsimport { webfont } from "webfont"; import { optimize } from "svgo"; await webfont({ files: "src/icons/**/*.svg", glyphContentTransformFn: async (glyph) => optimize(glyph.contents, { plugins: ["preset-default"] }).data, });Adjust SVGO plugins to your icons; test output glyphs in the HTML preview.
See also docs/migration/issue-0612-svg-use-transform.md.
unicode-range enabled but ligatures stopped working
What error appeared
There is often no error. Icons via class + codepoint (.icon-phone::before) still work. Typing ligature names (phone_call) shows fallback text or blank glyphs after you turned on unicodeRange (#322).
Why it usually happens
unicode-rangeis opt-in (default off). When enabled, webfont emits a range from private-use code points only (for exampleU+EA01-EA1D). Ligature identifier strings are ASCII and are not included in that range.- The browser applies the icon font only for code points inside
unicode-range. Forphone_call, lettersp,h,o, … use a system font, so OpenTypeligacannot substitute the icon glyph. - This is expected CSS behavior —
unicode-rangeoptimizes multi-font pages; ligature-by-name needs the icon font (or a second@font-facewithout range) for ASCII input.
Steps to try to resolve
Use class + codepoint icons in production when
unicode-rangeis enabled (recommended with multiple icon fonts on one page).Disable
unicode-rangeif you rely on ligature names: omit--unicode-range/unicodeRange: true(default), or setunicodeRange: falsein config.HTML preview: built-in
htmlomitsunicode-rangewhen bothtemplateFontLigaturesandunicodeRangeare enabled so the Ligature section can still preview names. Generatedcss/scsstemplates do not — enableunicodeRangeonly when you accept the ligature trade-off.See also Ligature names show text but no icon.
Ligature names show text but no icon (HTML preview)
What error appeared
There is often no error. The built-in html preview lists icon names under Ligature, but the large icon above each name is blank or shows fallback serif text (for example bleach, drip_dry) while the Class section (.font-icon-name::before) shows the glyph.
Why it usually happens
- Ligature mode types the icon name (for example
phone_call) as normal text. The browser must apply OpenTypeligaso that character sequence maps to the icon glyph in the font. - Icon fonts typically do not include regular Latin letters — without
liga, the browser looks upp,h,o, … in the icon font, finds no glyphs, and renders nothing. - From webfont 12.x onward, the built-in
htmltemplate addsfont-feature-settings: "liga" 1on#icon-ligaturesby default (templateFontLigatures, on by default).unicode-rangeis off by default; if you enable it, ligature preview may break unless the HTML template strips the rule (see above). unicode-rangeon@font-facelimits which characters use the icon font. When enabled, the computed range covers only private-use code points. Ligature names are ASCII, so the browser keeps a fallback font for those letters andliganever runs.
Steps to try to resolve
Regenerate with a current webfont version (
ligapreview CSS on by default;unicode-rangeoff unless you opt in).If you enabled
unicodeRange, seeunicode-rangeenabled but ligatures stopped working.In your own CSS or template, enable ligatures where you type icon names:
css.my-icon-font { font-family: "my-icon-font"; font-feature-settings: "liga" 1; font-variant-ligatures: common-ligatures; }Prefer class + codepoint for production (works without
ligaand withunicode-range):html<i class="my-icon-font my-icon-font-phone-call"></i>Disable preview CSS if you manage ligatures yourself:
templateFontLigatures: falseor--no-template-font-ligatures.Ensure
ligaturesis enabled when using ligature names (--ligaturesadds ligature glyphs to the font).
Browser hangs or extreme slowdown (large icon webfont, Firefox on Windows)
What error appeared
There is often no build error. After upgrading webfont (for example v9 → v11+) and shipping a large icon font (thousands of glyphs — Material Design Icons, Tabler, etc.), users report:
- Firefox on Windows freezing for seconds when opening a tab or switching tabs
- High CPU in DirectWrite /
LigatureSubstitutionduring layout reflow - Chrome slower than before; macOS/Linux often less affected
See #558, MaterialDesign#6519, and tabler-icons#1327.
Why it usually happens
- From webfont 12.x,
ligaturesdefault tofalse(performance on large fonts). webfont 10.x–11.x enabled ligatures by default — upgrading from those versions without regenerating fonts is a common trigger for this issue. - When
ligatures: true(or--ligatures), each icon name is also encoded as an OpenType ligature (for examplephone_call→ glyph). - Thousands of ligatures produce a very large GSUB table. Windows Firefox uses DirectWrite to process ligature lookups during layout — reflow can take seconds per tab switch.
- WOFF2 is not the root cause — the same ligature-heavy TTF/WOFF triggers the behavior. Fonts built without ligatures perform normally.
Steps to try to resolve
Keep ligatures disabled (default on webfont 12.x). On older releases use
--no-ligatures/ligatures: false:shellwebfont "icons/*.svg" -d dist/fontsjsawait webfont({ files: "icons/**/*.svg" });Only enable ligatures if you need ligature-by-name text and accept the performance cost:
shellwebfont "icons/*.svg" -d dist/fonts --ligaturesUse class + codepoint CSS in the app (
.mdi-phone::before { content: "\\f001"; }) — not ligature-by-name text.In CSS, if you cannot regenerate the font yet, try
font-variant-ligatures: noneon icon classes — this helps only when the page does not rely onligafor icons; regenerating without ligatures is more reliable.webfont warns when glyph count exceeds 2000 with ligatures enabled (stdout). Do not pass
--ligatureson MDI-scale sets.Prefer
@mdi/svg/ SVG or JS icon packages for web apps when the upstream project documents webfont alternatives.
Can't resolve fs (webpack / React / Vite client bundle)
What error appeared
Bundlers fail while building client code with errors such as:
Module not found: Error: Can't resolve 'fs'Or at runtime in the browser (often with older webfont versions bundled into the app):
TypeError: Cannot read property 'dirname' of undefined
at .../node_modules/glob-parent/index.jsThe stack trace often includes globby, fast-glob, glob-parent, cosmiconfig, or webfont/dist/standalone.js (#198, #496).
Why it usually happens
webfontis Node.js-only. It reads SVG files from disk, runsglobby, and uses other Node built-ins. It is meant for build time, not inside browser/React component bundles.- The package was imported from client code (for example
import webfont from "webfont"inApp.js). Webpack/Vite tries to bundle the full Node implementation. Without Node’spathmodule, dependencies such asglob-parentthrowCannot read property 'dirname' of undefined(#496). - From webfont 12.x, the package
browser/exportsdefault entry resolves to a small stub that rejects with a clear message instead of pulling inglobby— but you must still not callwebfont()from client code; generate fonts in Node and ship the output files.
Steps to try to resolve
Do not import
webfontfrom client-side code. Generate fonts in an npm script, a Node build step, or CI:shellwebfont "src/icons/*.svg" -d public/fonts -t cssUse the webfont-webpack-plugin (or an equivalent build hook) so generation runs in Node during compilation — not in the browser bundle.
Programmatic API — Node only:
jsimport { webfont } from "webfont"; await webfont({ files: "src/icons/*.svg", dest: "public/fonts" });Run this from a
.mjsscript,vite.config.ts, or webpack config — never from React components loaded in the browser.If a dependency still pulls
webfontinto the client bundle, mark it external or move the import to a Node-only file. With webfont 12.x and webpack 5+ / modern Vite, the packagebrowserfield andexportsdefault resolve todist/browser.js, which throws a clear error instead of bundlingfs/glob-parent. On 11.x or webpack 4 withoutbrowserresolution, you may seedirname of undefineduntil you stop importingwebfontfrom client code.Browser-based generation is not supported on the current release; see #708 for a possible future Web Worker spike.