gohugoio/hugo · error

failed to compile cache buster target

Error message

failed to compile cache buster target %q: %w

What it means

Hugo's cache-buster config (CacheBuster.CompileConfig in config/commonConfig.go) compiles a Source regexp, then on each match substitutes Source capture groups ($1, $2, ...) into the Target string and recompiles that substituted string as a regexp. This error fires when the resulting Target string is not valid Go regexp syntax (regexp.Compile at line 434 fails). It is a runtime/compile error surfaced during Hugo's config compile phase, not at file load time.

Solutions

  1. Test the Target string in isolation with regexp.Compile in a scratch program before putting it in config.
  2. Avoid injecting captured group content ($1) into Target unless you are certain it cannot contain regexp metacharacters; escape it or match it literally.
  3. Ensure the number of capturing groups in Source matches the highest $N referenced in Target.
  4. If literal matching is intended, anchor and escape the Target (e.g. use \. for a literal dot).

Example fix

// before (config)
[cacheBuster]
  source = "(\\.(css|js))$"
  target = "$1?v="  // $1 expands to '.css' -> '.css?v=' which is valid, but if source
                 // captures user data this breaks; prefer a static target

// after
[cacheBuster]
  source = "\\.(css|js)$"
  target = "\\.(css|js)$"  // static, valid regex, no group injection
Defensive patterns

Strategy: validation

Validate before calling

// Validate a cacheBuster Source/Target pair before applying it.
import "regexp"

func ValidateCacheBuster(source, target string, sampleMatches ...string) error {
	srcRe, err := regexp.Compile(source)
	if err != nil {
		return fmt.Errorf("source: %w", err)
	}
	for _, s := range sampleMatches {
		m := srcRe.FindStringSubmatch(s)
		if m == nil {
			continue
		}
		cur := target
		for i, g := range m[1:] {
			cur = strings.ReplaceAll(cur, fmt.Sprintf("$%d", i+1), g)
		}
		if _, err := regexp.Compile(cur); err != nil {
			return fmt.Errorf("target after substitution %q: %w", cur, err)
		}
	}
	return nil
}

Try / catch

// CacheBuster.CompileConfig returns the error from config compilation;
// surface it at config-load time rather than at build time.
if err := cacheBuster.CompileConfig(logger); err != nil {
    return fmt.Errorf("config: %w", err)
}

Prevention

When it happens

Trigger: Configuring a cacheBuster where Source captures content containing regexp metacharacters that, once substituted into Target, break the regex (e.g. Target = "$1.css" and $1 expands to "file(name"); or hardcoding a Target value like "(css|.css" that is itself invalid. The compile is deferred until the closure actually runs against a matched asset path.

Common situations: Author sets cacheBuster.Target expecting literal matching but writes regex syntax; capture groups from Source inject unescaped user/path content into Target; mismatched number of $N references versus Source capture groups producing dangling references.

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/a52d9d52c5927b35. Report an issue: GitHub.

Appendix: source

Thrown at config/commonConfig.go:436

		m := sourceRe.FindStringSubmatch(s)
		matchString := "no match"
		match := m != nil
		if match {
			matchString = "match!"
		}
		debugl.Logf("Matching %q with source %q: %s", s, source, matchString)
		if !match {
			return nil
		}
		groups := m[1:]
		currentTarget := target
		// Replace $1, $2 etc. in target.
		for i, g := range groups {
			currentTarget = strings.ReplaceAll(target, fmt.Sprintf("$%d", i+1), g)
		}
		targetRe, err := regexp.Compile(currentTarget)
		if err != nil {
			compileErr = fmt.Errorf("failed to compile cache buster target %q: %w", currentTarget, err)
			return nil
		}
		return func(ss string) bool {
			match := targetRe.MatchString(ss)
			matchString := "no match"
			if match {
				matchString = "match!"
			}
			logger.Debugf("Matching %q with target %q: %s", ss, currentTarget, matchString)

			return match
		}
	}
	return compileErr
}

func (r Redirect) IsZero() bool {
	return r.From == "" && r.FromRe == ""

View on GitHub (pinned to 52c9bd7908)