jekyll-skyhook

jekyll-skyhook

Modern asset processing for Jekyll with image transformations

Jekyll-Skyhook ✈️

Modern asset processing for Jekyll with image transformations:

  • Image transformations - resize, format conversion (WebP, AVIF, etc.)
  • Responsive images - automatic srcset generation
  • Cache-busting digests - fingerprinted asset URLs
  • CSS url() rewriting - automatic asset path updates
  • Development file watcher - automatic regeneration
  • Manifest-based caching - avoid duplicate processing

Installation

  1. Add to your Gemfile:
group :jekyll_plugins do
  gem 'jekyll-skyhook'
end

And then run

bundle install
  1. Install image processing library:

For ImageMagick (default):

# Ubuntu/Debian:
sudo apt-get install imagemagick
# macOS:
brew install imagemagick

For libvips (faster alternative):

# Ubuntu/Debian:
sudo apt-get install libvips
# macOS:
brew install vips
  1. Add _digested directory to .gitignore

Configuration

Add to _config.yml:

skyhook:
  # Directories to process (default: ['assets'])
  asset_dirs: [assets, images]

  # When to digest assets (default: true)
  # Can be:
  #   - true (always digest)
  #   - false (never digest)
  #   - string (digest in specific environment)
  #   - array (digest in multiple environments)
  digest_assets: true
  # Examples:
  # digest_assets: true
  # digest_assets: false
  # digest_assets: production
  # digest_assets: [staging, production]

  # Directory for digested assets (default: '_digested')
  digest_dir: '_digested'

  # Image processing library (default: 'mini_magick')
  # Options: 'mini_magick' or 'vips'
  # vips is faster but requires libvips to be installed
  image_processor: mini_magick

# Include digested files in build
include:
  - _digested

Usage

Basic Asset Digesting

Use the {% asset %} tag for regular assets with cache-busting digests:

<link rel="stylesheet" href="{% asset assets/styles.css %}">
<script src="{% asset assets/app.js %}"></script>

Image Transformations

Use the {% image_transform %} tag with bracket syntax for image transformations:

<!-- Resize image -->
{% image_transform assets/hero.jpg[width="400"] %}

<!-- Convert format -->
{% image_transform assets/hero.jpg[format="webp"] %}

<!-- Multiple transformations -->
{% image_transform assets/hero.jpg[width="800"][format="avif"] %}

<!-- Use in img tag -->
<img src="{% image_transform assets/hero.jpg[width="400"][format="webp"] %}"
     alt="Hero image" loading="lazy">

Supported transformations:

  • width="400" - Resize to maximum width (maintains aspect ratio)
  • height="300" - Resize to maximum height (maintains aspect ratio)
  • format="webp" - Convert to WebP, AVIF, PNG, or JPEG

Responsive Images with Srcset

Use the {% srcset %} tag to generate responsive image sets:

<!-- Generate multiple sizes in original format -->
{% srcset assets/hero.jpg 400 800 1200 %}

<!-- Generate multiple sizes in specific format -->
{% srcset assets/hero.jpg[format="webp"] 400 800 1200 %}

Complete Picture Element Example

Create modern responsive images with multiple formats:

<picture>
  <!-- AVIF sources -->
  <source
    type="image/avif"
    {% srcset assets/hero.jpg[format="avif"] 400 800 1200 %}
    sizes="(max-width: 600px) 100vw, 50vw">

  <!-- WebP fallback -->
  <source
    type="image/webp"
    {% srcset assets/hero.jpg[format="webp"] 400 800 1200 %}
    sizes="(max-width: 600px) 100vw, 50vw">

  <!-- Original format fallback + lazy loading -->
  <img
    src="{% image_transform assets/hero.jpg[width="800"] %}"
    {% srcset assets/hero.jpg 400 800 1200 %}
    sizes="(max-width: 600px) 100vw, 50vw"
    alt="Description of the image"
    loading="lazy">
</picture>

Liquid Variables

You can use Liquid variables in transformations:

{% assign mobile_width = "400" %}
{% assign format = "webp" %}

<img src="{% image_transform {{ page.featured_image }}[width="{{ mobile_width }}"][format="{{ format }}"] %}">

Manual Image Processing

For build scripts or advanced use cases, you can manually store processed images:

require 'image_processing/mini_magick'

# Process image
processed = ImageProcessing::MiniMagick
  .source("assets/hero.jpg")
  .resize_to_limit(400, 400)
  .convert("webp")
  .call

# Store in Skyhook manifest
skyhook = Jekyll::Skyhook.instance(site)
skyhook.store_version("assets/hero.jpg", {format: "webp", width: "400"}, processed.path)

File Structure

your-site/
├── assets/
│   ├── hero.jpg          # Original image
│   ├── styles.css        # Original CSS
│   └── app.js           # Original JS
├── _digested/           # Generated (add to .gitignore)
│   └── assets/
│       ├── hero-width400-abc123.webp
│       ├── hero-width800-def456.webp
│       ├── styles-xyz789.css
│       └── app-uvw012.js
├── _site/               # Jekyll build output
│   └── _digested/       # Digested assets maintain structure
│       └── assets/
│           ├── hero-width400-abc123.webp
│           ├── styles-xyz789.css
│           └── app-uvw012.js
└── .jekyll-cache/
    └── skyhook/
        └── assets-manifest.json  # Tracks all transformations

How It Works

  1. Build time: Skyhook processes all assets in configured directories
  2. Transformations: Images are resized/converted to different formats
  3. Digests: Each transformation gets a unique hash based on content
  4. Manifest: All mappings stored in assets-manifest.json for fast lookups
  5. Static files: Transformed assets automatically added to Jekyll's static file list
  6. Development: File watcher regenerates assets when source files change

Performance

  • Caching: Identical transformations are cached and reused
  • Incremental: Only changed assets are reprocessed
  • Manifest: Fast lookups avoid redundant processing
  • Parallel: Multiple transformations can be processed concurrently

Troubleshooting

ImageMagick not found

# Install ImageMagick
brew install imagemagick  # macOS
sudo apt-get install imagemagick  # Ubuntu/Debian

Assets not found

  • Ensure asset directories exist in _config.yml
  • Check that _digested is included in Jekyll build
  • Verify file paths are correct (relative to site source)

Large build times

  • Limit asset directories to only what needs processing
  • Use digest_assets: false in development if needed
  • Consider pre-generating large image sets

Drafts not working

  • Ensure you're running Jekyll with --drafts flag when working on drafts
  • Assets are processed regardless of draft visibility
  • Both jekyll serve --drafts and jekyll build --drafts are supported
jekyll logo

Want a Jekyll website built?

Hire a Jekyll developer