Agent Skill
2/7/2026

hugo-asset-pipeline

This skill should be used when the user mentions "scss", "sass", "css processing", "javascript bundling", "js.Build", "hugo pipes", "asset pipeline", "fingerprint", "cache busting", "minify assets", "image processing", "responsive images", "webp", "toCSS", "resources.Get", "resources.Concat", "sri", "subresource integrity", or any Hugo asset processing topics. Provides comprehensive guidance on Hugo's built-in asset pipeline for SCSS compilation, JavaScript bundling, image optimization, and cache management.

H
hughescr
1GitHub Stars
1Views
npx skills add hughescr/claude-code-config

SKILL.md

Namehugo-asset-pipeline
DescriptionThis skill should be used when the user mentions "scss", "sass", "css processing", "javascript bundling", "js.Build", "hugo pipes", "asset pipeline", "fingerprint", "cache busting", "minify assets", "image processing", "responsive images", "webp", "toCSS", "resources.Get", "resources.Concat", "sri", "subresource integrity", or any Hugo asset processing topics. Provides comprehensive guidance on Hugo's built-in asset pipeline for SCSS compilation, JavaScript bundling, image optimization, and cache management.

name: hugo-asset-pipeline description: This skill should be used when the user mentions "scss", "sass", "css processing", "javascript bundling", "js.Build", "hugo pipes", "asset pipeline", "fingerprint", "cache busting", "minify assets", "image processing", "responsive images", "webp", "toCSS", "resources.Get", "resources.Concat", "sri", "subresource integrity", or any Hugo asset processing topics. Provides comprehensive guidance on Hugo's built-in asset pipeline for SCSS compilation, JavaScript bundling, image optimization, and cache management.

Hugo Asset Pipeline (Hugo Pipes)

Overview

Hugo Pipes is Hugo's built-in asset processing pipeline that replaces traditional bundlers like webpack, Vite, or esbuild. Hugo projects DO have a build step - it's just handled by Hugo itself, not external tools.

CRITICAL: When working on Hugo projects, use Hugo Pipes for all asset processing. Do NOT add webpack, Vite, Rollup, or other JavaScript bundlers - Hugo handles this natively.

Hugo Extended Requirement

SCSS/SASS compilation requires Hugo Extended edition. Check your version:

hugo version
# Look for "extended" in the output: hugo v0.XXX.X-extended

If you need Extended and don't have it, install via:

# macOS
brew install hugo

# Or download extended version from GitHub releases

Core Hugo Pipes Functions

resources.Get - Load Assets

Load files from the assets/ directory (NOT static/):

{{ $styles := resources.Get "scss/main.scss" }}
{{ $script := resources.Get "js/app.js" }}
{{ $image := resources.Get "images/hero.jpg" }}

Important: Assets MUST be in assets/ directory, not static/. The static/ directory is for files that need no processing.

resources.Concat - Combine Files

Merge multiple files into one:

{{ $vendorJS := resources.Get "js/vendor/jquery.js" }}
{{ $appJS := resources.Get "js/app.js" }}
{{ $allJS := slice $vendorJS $appJS | resources.Concat "js/bundle.js" }}

toCSS - SCSS/SASS Compilation

Compile SCSS to CSS (requires Hugo Extended):

{{ $opts := dict "transpiler" "libsass" "targetPath" "css/style.css" }}
{{ $styles := resources.Get "scss/main.scss" | toCSS $opts }}

Options:

  • transpiler: "libsass" (default) or "dartsass"
  • targetPath: Output path relative to publishDir
  • outputStyle: "nested", "expanded", "compact", "compressed"
  • precision: Decimal precision for calculations
  • enableSourceMap: true/false

js.Build - JavaScript Bundling

Bundle JavaScript with Hugo's built-in esbuild:

{{ $opts := dict
    "targetPath" "js/app.js"
    "minify" true
    "target" "es2020"
    "format" "esm"
}}
{{ $js := resources.Get "js/main.js" | js.Build $opts }}

Options:

  • targetPath: Output filename
  • minify: true/false
  • target: "es2015", "es2017", "es2020", etc.
  • format: "iife", "cjs", "esm"
  • externals: Array of packages to exclude from bundle
  • defines: Global constant replacements
  • sourceMap: "inline", "external", or empty

minify - Minification

Minify CSS, JS, JSON, HTML, SVG, XML:

{{ $styles := resources.Get "scss/main.scss" | toCSS | minify }}
{{ $js := resources.Get "js/app.js" | js.Build | minify }}

fingerprint - Cache Busting

Add content hash to filename for cache invalidation:

{{ $styles := resources.Get "scss/main.scss" | toCSS | minify | fingerprint }}
{{/* Output: /css/style.a1b2c3d4.css */}}

<link rel="stylesheet" href="{{ $styles.RelPermalink }}">

Fingerprint options:

  • fingerprint - SHA256 (default)
  • fingerprint "sha384" - SHA384
  • fingerprint "sha512" - SHA512
  • fingerprint "md5" - MD5 (not recommended)

Development vs Production Patterns

Conditional Processing with hugo.IsServer

{{ $styles := resources.Get "scss/main.scss" }}

{{ if hugo.IsServer }}
  {{/* Development: source maps, no minification */}}
  {{ $opts := dict "enableSourceMap" true "outputStyle" "expanded" }}
  {{ $styles = $styles | toCSS $opts }}
{{ else }}
  {{/* Production: minified and fingerprinted */}}
  {{ $styles = $styles | toCSS | minify | fingerprint }}
{{ end }}

<link rel="stylesheet" href="{{ $styles.RelPermalink }}">

Using hugo.IsProduction

{{ $js := resources.Get "js/app.js" }}

{{ $opts := dict "targetPath" "js/bundle.js" }}
{{ if hugo.IsProduction }}
  {{ $opts = merge $opts (dict "minify" true "target" "es2017") }}
{{ else }}
  {{ $opts = merge $opts (dict "sourceMap" "inline") }}
{{ end }}

{{ $js = $js | js.Build $opts }}

SCSS Compilation Patterns

Basic SCSS Setup

Directory structure:

assets/
  scss/
    main.scss
    _variables.scss
    _mixins.scss
    components/
      _buttons.scss
      _forms.scss

main.scss:

@import "variables";
@import "mixins";
@import "components/buttons";
@import "components/forms";

Template:

{{ $styles := resources.Get "scss/main.scss" | toCSS | minify | fingerprint }}
<link rel="stylesheet" href="{{ $styles.RelPermalink }}">

SCSS with npm Packages (Bootstrap Example)

Install with Bun:

bun add bootstrap

Configure module mounts in hugo.toml:

[[module.mounts]]
  source = "assets"
  target = "assets"

[[module.mounts]]
  source = "node_modules/bootstrap/scss"
  target = "assets/scss/bootstrap"

[[module.mounts]]
  source = "node_modules/bootstrap/dist/js"
  target = "assets/js/bootstrap"

Import in your SCSS:

// assets/scss/main.scss
@import "bootstrap/bootstrap";

// Or import specific components
@import "bootstrap/functions";
@import "bootstrap/variables";
@import "bootstrap/mixins";
@import "bootstrap/grid";

JavaScript Bundling Patterns

Basic JavaScript Bundle

{{ $js := resources.Get "js/main.js" | js.Build (dict "minify" hugo.IsProduction) | fingerprint }}
<script src="{{ $js.RelPermalink }}"></script>

ES Modules with Imports

assets/js/main.js:

import { initNav } from './components/navigation.js';
import { setupForms } from './components/forms.js';

document.addEventListener('DOMContentLoaded', () => {
  initNav();
  setupForms();
});

Template:

{{ $opts := dict
    "targetPath" "js/bundle.js"
    "format" "iife"
    "minify" hugo.IsProduction
}}
{{ $js := resources.Get "js/main.js" | js.Build $opts | fingerprint }}
<script src="{{ $js.RelPermalink }}"></script>

npm Package Integration

Install packages:

bun add alpinejs

Mount in hugo.toml:

[[module.mounts]]
  source = "node_modules/alpinejs/dist"
  target = "assets/js/alpine"

Import in your JS:

// assets/js/main.js
import Alpine from 'js/alpine/module.esm.js';
window.Alpine = Alpine;
Alpine.start();

Image Processing

Basic Resizing

{{ $image := resources.Get "images/hero.jpg" }}
{{ $resized := $image.Resize "800x" }}
<img src="{{ $resized.RelPermalink }}" width="{{ $resized.Width }}" height="{{ $resized.Height }}">

Resize Operations

{{/* Resize to width, maintain aspect ratio */}}
{{ $img := $image.Resize "800x" }}

{{/* Resize to height, maintain aspect ratio */}}
{{ $img := $image.Resize "x600" }}

{{/* Resize to exact dimensions (may distort) */}}
{{ $img := $image.Resize "800x600" }}

{{/* Fit within bounds, maintain aspect ratio */}}
{{ $img := $image.Fit "800x600" }}

{{/* Fill dimensions, crop as needed */}}
{{ $img := $image.Fill "800x600 Center" }}

Fill anchor positions: Center, TopLeft, Top, TopRight, Left, Right, BottomLeft, Bottom, BottomRight, Smart

WebP Conversion

{{ $original := resources.Get "images/photo.jpg" }}
{{ $webp := $original.Resize "800x webp" }}
{{ $jpg := $original.Resize "800x" }}

<picture>
  <source srcset="{{ $webp.RelPermalink }}" type="image/webp">
  <img src="{{ $jpg.RelPermalink }}" alt="Photo">
</picture>

Responsive Images with srcset

{{ $image := resources.Get "images/hero.jpg" }}
{{ $small := $image.Resize "400x" }}
{{ $medium := $image.Resize "800x" }}
{{ $large := $image.Resize "1200x" }}

<img
  src="{{ $medium.RelPermalink }}"
  srcset="{{ $small.RelPermalink }} 400w,
          {{ $medium.RelPermalink }} 800w,
          {{ $large.RelPermalink }} 1200w"
  sizes="(max-width: 400px) 400px, (max-width: 800px) 800px, 1200px"
  alt="Hero image">

Image Fingerprinting

{{ $image := resources.Get "images/logo.png" }}
{{ $processed := $image.Resize "200x" | fingerprint }}
<img src="{{ $processed.RelPermalink }}" alt="Logo">

Subresource Integrity (SRI)

SRI ensures loaded resources haven't been tampered with. Use with fingerprint:

{{ $styles := resources.Get "scss/main.scss" | toCSS | minify | fingerprint "sha384" }}
<link rel="stylesheet" href="{{ $styles.RelPermalink }}"
      integrity="{{ $styles.Data.Integrity }}"
      crossorigin="anonymous">

{{ $js := resources.Get "js/app.js" | js.Build | minify | fingerprint "sha384" }}
<script src="{{ $js.RelPermalink }}"
        integrity="{{ $js.Data.Integrity }}"
        crossorigin="anonymous"></script>

Why SRI matters:

  • Protects against CDN compromises
  • Detects file tampering
  • Required for security compliance in many contexts
  • Browser refuses to load resource if hash doesn't match

Complete Production Template

A full baseof.html head section example:

{{ $styles := resources.Get "scss/main.scss" }}
{{ $js := resources.Get "js/main.js" }}

{{ if hugo.IsServer }}
  {{/* Development */}}
  {{ $styles = $styles | toCSS (dict "enableSourceMap" true) }}
  {{ $js = $js | js.Build (dict "sourceMap" "inline") }}
  <link rel="stylesheet" href="{{ $styles.RelPermalink }}">
  <script src="{{ $js.RelPermalink }}" defer></script>
{{ else }}
  {{/* Production */}}
  {{ $styles = $styles | toCSS | minify | fingerprint "sha384" }}
  {{ $js = $js | js.Build (dict "minify" true "target" "es2017") | fingerprint "sha384" }}
  <link rel="stylesheet" href="{{ $styles.RelPermalink }}"
        integrity="{{ $styles.Data.Integrity }}" crossorigin="anonymous">
  <script src="{{ $js.RelPermalink }}" defer
          integrity="{{ $js.Data.Integrity }}" crossorigin="anonymous"></script>
{{ end }}

Module Mounts Reference

Full hugo.toml example with multiple npm packages:

[module]
  [[module.mounts]]
    source = "assets"
    target = "assets"

  [[module.mounts]]
    source = "static"
    target = "static"

  # Bootstrap SCSS
  [[module.mounts]]
    source = "node_modules/bootstrap/scss"
    target = "assets/scss/bootstrap"

  # Bootstrap JS
  [[module.mounts]]
    source = "node_modules/bootstrap/dist/js"
    target = "assets/js/bootstrap"

  # Alpine.js
  [[module.mounts]]
    source = "node_modules/alpinejs/dist"
    target = "assets/js/alpine"

  # FontAwesome
  [[module.mounts]]
    source = "node_modules/@fortawesome/fontawesome-free/scss"
    target = "assets/scss/fontawesome"

  [[module.mounts]]
    source = "node_modules/@fortawesome/fontawesome-free/webfonts"
    target = "static/webfonts"

Troubleshooting

"SCSS compilation failed"

  • Ensure you have Hugo Extended edition
  • Check SCSS syntax errors in terminal output
  • Verify all imported partials exist (files starting with _)

"Resource not found"

  • File must be in assets/ not static/
  • Path in resources.Get is relative to assets/
  • Check filename case sensitivity

"js.Build failed"

  • Check for JavaScript syntax errors
  • Verify all imports resolve correctly
  • Ensure npm packages are installed and mounted

Images not processing

  • Image must be in assets/ directory
  • Use resources.Get, not direct path references
  • Check supported formats: jpg, png, gif, webp, tiff, bmp

Key Differences from Traditional Bundlers

TraditionalHugo Pipes
webpack.config.jshugo.toml + templates
npm run buildhugo (builds everything)
npm run devhugo server
dist/ folderpublic/ folder
Manual cache bustingBuilt-in fingerprint
Complex configurationTemplate-based, declarative
Skills Info
Original Name:hugo-asset-pipelineAuthor:hughescr