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
- Add to your
Gemfile
:
group :jekyll_plugins do
gem 'jekyll-skyhook'
end
And then run
bundle install
- 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
- 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
- Build time: Skyhook processes all assets in configured directories
- Transformations: Images are resized/converted to different formats
- Digests: Each transformation gets a unique hash based on content
-
Manifest: All mappings stored in
assets-manifest.json
for fast lookups - Static files: Transformed assets automatically added to Jekyll's static file list
- 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
andjekyll build --drafts
are supported