gohugoio/hugo · error

resources in Concat must be of the same Media Type, got %q a

Error message

resources in Concat must be of the same Media Type, got %q and %q

What it means

`resources.Concat` requires every resource in the slice to share the same media type (e.g. all `text/css` or all `text/javascript`), because the concatenated output resource must carry a single media type and Hugo cannot safely infer one from a mixed set. The check compares each resource's `MediaType().Type` against the first one and fails on the first mismatch.

Source

Thrown at resources/resource_factories/bundler/bundler.go:92

func (r *multiReadSeekCloser) Close() error {
	for _, s := range r.sources {
		s.Close()
	}
	return nil
}

// Concat concatenates the list of Resource objects.
func (c *Client) Concat(targetPath string, r resource.Resources) (resource.Resource, error) {
	targetPath = path.Clean(targetPath)
	return c.rs.ResourceCache.GetOrCreate(targetPath, func() (resource.Resource, error) {
		var resolvedm media.Type

		// The given set of resources must be of the same Media Type.
		// We may improve on that in the future, but then we need to know more.
		for i, rr := range r {
			if i > 0 && rr.MediaType().Type != resolvedm.Type {
				return nil, fmt.Errorf("resources in Concat must be of the same Media Type, got %q and %q", rr.MediaType().Type, resolvedm.Type)
			}
			resolvedm = rr.MediaType()
		}

		idm := c.rs.Cfg.NewIdentityManager()

		// Re-create on structural changes.
		idm.AddIdentity(identity.StructuralChangeAdd, identity.StructuralChangeRemove)

		// Add the concatenated resources as dependencies to the composite resource
		// so that we can track changes to the individual resources.
		idm.AddIdentityForEach(identity.ForEeachIdentityProviderFunc(
			func(f func(identity.Identity) bool) bool {
				var terminate bool
				for _, rr := range r {
					identity.WalkIdentitiesShallow(rr, func(depth int, id identity.Identity) bool {
						terminate = f(id)
						return terminate

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Transform all resources to the target type before Concat, e.g. pipe SCSS through `css.Sass` (and JS through `js.Build`) first.
  2. Tighten the glob so only one file type is matched, e.g. `resources.Match "css/*.css"` instead of a wildcard over mixed assets.
  3. Print each resource's `.MediaType` in the template to find the odd one out, then fix its pipeline or the `mediaTypes` config.

Example fix

{{/* before */}}
{{ $css := slice (resources.Get "main.scss") (resources.Get "extra.css") | resources.Concat "bundle.css" }}
{{/* after */}}
{{ $main := resources.Get "main.scss" | css.Sass }}
{{ $css := slice $main (resources.Get "extra.css") | resources.Concat "bundle.css" }}
Defensive patterns

Strategy: validation

Validate before calling

{{/* pre-check media types before Concat in a template */}}
{{ $files := slice (resources.Get "a.js") (resources.Get "b.js") }}
{{ $mt := (index $files 0).MediaType.Type }}
{{ range $files }}
  {{ if ne .MediaType.Type $mt }}
    {{ errorf "Concat mix: %s vs %s (%s)" $mt .MediaType.Type .Name }}
  {{ end }}
{{ end }}
{{ $bundle := $files | resources.Concat "bundle.js" }}

Prevention

When it happens

Trigger: Calling `resources.Concat $target $slice` in a template where the slice mixes types — e.g. a `.css` file with a `.js` file, or a `.scss` resource that was not piped through `css.Sass` first (media type `text/x-scss` vs `text/css`), or minified vs source assets whose extensions map to different configured media types.

Common situations: Forgetting to run `toCSS`/`css.Sass` on SCSS resources before concatenating them with plain CSS; globbing too broadly (`resources.Match "assets/**"`) and picking up source maps or unrelated files; custom `mediaTypes` config mapping an extension to an unexpected type; template refactors that reorder pipes so Concat runs before the transform.

Related errors


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