gohugoio/hugo · error

failed to build JS batch: %w

Error message

failed to build JS batch: %w

What it means

During the per-group build loop inside build(), buildBatchGroup (batch.go:506-509) executes the runner template and creates a resource via FromString; if either step fails the error is wrapped as 'failed to build JS batch'. Unlike 397 (which carries the batch id), this variant wraps the group-level failure without the id and is one layer deeper.

Source

Thrown at internal/js/esbuild/batch.go:508

				return k.scriptID == vv.Key()
			}
			for _, vvv := range instances.Filter(predicate) {
				bt.Instances = append(bt.Instances, scriptInstanceBatchTemplateContext{opts: vvv})
			}

			t.Scripts = append(t.Scripts, bt)
		}

		for _, vv := range g.runnersOptions.ByKey() {
			runnerKeyPath := keyPath + "_" + vv.Key().String()
			runnerImpPath := paths.AddLeadingSlash(runnerKeyPath + "_runner" + vv.Compiled().Resource.MediaType().FirstSuffix.FullSuffix)
			t.Runners = append(t.Runners, scriptRunnerTemplateContext{opts: vv, Import: runnerImpPath})
			addResource(g.id, runnerImpPath, vv.Compiled().Resource, false)
		}

		r, s, err := b.client.buildBatchGroup(ctx, t)
		if err != nil {
			return nil, fmt.Errorf("failed to build JS batch: %w", err)
		}

		state.importerImportContext.Set(s, importContext{
			name:           s,
			resourceGetter: nil,
			dm:             g.dependencyManager,
		})

		addResource(g.id, s, r, true)
	}

	mediaTypes := b.client.d.ResourceSpec.MediaTypes()

	externalOptions := b.configOptions.Compiled().Options
	if externalOptions.Format == "" {
		externalOptions.Format = "esm"
	}
	if externalOptions.Format != "esm" {

View on GitHub (pinned to 52c9bd7908)

Solutions

  1. Inspect the wrapped error to distinguish template-execution failure from resource-creation failure.
  2. For template errors, check the runner template against the current batchGroupTemplateContext fields.
  3. For FromString errors, verify each group has valid scripts and a unique keyPath.
  4. Reproduce with a single group to isolate the failing one.

Example fix

// before: runner template references {{ .MissingField }}

// after: use a field that exists on batchGroupTemplateContext (e.g. {{ .ID }})
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure each group has at least one script and a unique keyPath before build.
func validateGroup(g *scriptGroup) error {
    if g.id == "" { return fmt.Errorf("group id empty") }
    if len(g.scriptsOptions.ByKey()) == 0 && len(g.runnersOptions.ByKey()) == 0 {
        return fmt.Errorf("group %q has no scripts/runners", g.id)
    }
    return nil
}

Try / catch

// Wrap per-group build to surface a clear id in logs.
for _, g := range groups {
    if err := buildGroup(ctx, g); err != nil {
        return fmt.Errorf("group %q: %w", g.id, err)
    }
}

Prevention

When it happens

Trigger: The runner template (batchGroupTemplateContext) fails to execute (bad template syntax, missing field), or createClient.FromString rejects the generated JS (empty/invalid content, path conflict). Occurs once per script group in the batch.

Common situations: A runner template referencing a field removed from batchGroupTemplateContext after an upgrade; generated JS that is empty due to no scripts in the group; a virtual path collision from duplicate group ids.

Related errors


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