gohugoio/hugo · error

failed to resolve CSS @import "%s"; %s

Error message

failed to resolve CSS @import "%s"; %s

What it means

With `inlineImports = true`, Hugo's PostCSS/CSS transformer rewrites `@import` statements by inlining the imported file's content before invoking postcss. If the imported file can't be read from Hugo's asset filesystem, and `skipInlineImportsNotFound` is not enabled, the transform fails with a file-positioned error pointing at the offending @import line. Imports living in node_modules are the classic case, since they aren't in Hugo's assets mounts.

Source

Thrown at resources/resource_transformers/cssjs/inline_imports.go:122

			trackLine(i, offset, line)
		} else {
			path := strings.Trim(strings.TrimPrefix(line, importIdentifier), " \"';")
			filename := filepath.Join(basePath, path)
			imp.dependencyManager.AddIdentity(identity.CleanStringIdentity(filename))
			importContent, hash := imp.contentHash(filename)

			if importContent == nil {
				if imp.opts.SkipInlineImportsNotFound {
					trackLine(i, offset, line)
					continue
				}
				pos := text.Position{
					Filename:     inPath,
					LineNumber:   offset + 1,
					ColumnNumber: column + 1,
				}
				msgDetail := "if this import's source lives in node_modules, enable the skipInlineImportsNotFound option, see https://gohugo.io/functions/css/postcss/#skipinlineimportsnotfound"
				return 0, "", herrors.NewFileErrorFromFileInPos(fmt.Errorf("failed to resolve CSS @import \"%s\"; %s", filename, msgDetail), pos, imp.fs, nil)
			}

			i--

			if imp.contentSeen[hash] {
				i++
				// Just replace the line with an empty string.
				replacements = append(replacements, []string{line, ""}...)
				trackLine(i, offset, "IMPORT")
				continue
			}

			imp.contentSeen[hash] = true

			// Handle recursive imports.
			l, nested, err := imp.importRecursive(i+lineNum, string(importContent), filepath.ToSlash(filename))
			if err != nil {
				return 0, "", err

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. If the import's source lives in node_modules, set `skipInlineImportsNotFound = true` in the PostCSS options so Hugo leaves it for postcss-import to resolve (see https://gohugo.io/functions/css/postcss/#skipinlineimportsnotfound).
  2. Fix the @import path so it resolves relative to the importing file within assets.
  3. Mount the needed directory (e.g. node_modules package) into assets via module mounts so Hugo can inline it.

Example fix

// before (template)
{{ $css := $styles | css.PostCSS (dict "inlineImports" true) }}
// after
{{ $css := $styles | css.PostCSS (dict "inlineImports" true "skipInlineImportsNotFound" true) }}
Defensive patterns

Strategy: validation

Validate before calling

// before enabling inlineImports = true, verify every @import target exists in assets/
// grep -o '@import "[^"]*"' assets/css/main.css and check each file is mounted

Try / catch

opts := map[string]any{"inlineImports": true, "skipInlineImportsNotFound": true} // only if missing imports are acceptable

Prevention

When it happens

Trigger: `css.PostCSS (dict "inlineImports" true)` on a CSS file containing `@import "foo.css";` where foo.css does not exist relative to the importing file within Hugo's mounted assets; imports resolved by postcss-import from node_modules that Hugo's inliner can't see.

Common situations: Importing npm-package CSS (e.g. `@import "normalize.css";`) without mounting node_modules into assets; renamed or moved partial CSS files; path case mismatches on Linux CI vs macOS dev.

Related errors


AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31). Data as JSON: /data/errors/8e6ede53fdb83973.json. Report an issue: GitHub ↗.