Skip to content

Assets & CDN Filters

Nimbu provides powerful CDN integration for assets, with automatic image processing, optimization, and responsive image generation.

Asset URLs

asset_url

Generate URL for theme assets:

liquid
{{ 'style.css' | asset_url }}
<!-- Output: /assets/themes/theme-slug/style.css -->

<link rel="stylesheet" href="{{ 'main.css' | asset_url }}">
<script src="{{ 'app.js' | asset_url }}"></script>

theme_image_url

Get theme image URL:

liquid
{{ 'logo.png' | theme_image_url }}

<img src="{{ 'banner.jpg' | theme_image_url }}" alt="Banner">

global_asset_url

Access global site assets:

liquid
{{ 'shared-logo.png' | global_asset_url }}

<img src="{{ 'favicon.ico' | global_asset_url }}">

Image Processing with CDN

filter

Apply comprehensive image transformations:

liquid
{{ image.url | filter, width: '800px', height: '600px' }}

{{ product.image.url | filter, width: '400px', cropping: 'fill' }}

{{ photo.url | filter, width: '1200px', height: '800px', gravity: 'face', quality: 85 }}

Parameters:

ParameterDescriptionValues
widthTarget width'300px', '1200px'
heightTarget height'400px', '800px'
croppingCrop mode'fill', 'fit', 'limit', 'pad', 'scale'
gravityCrop focus'center', 'face', 'north', 'south', 'east', 'west'
effectVisual effect'grayscale', 'sepia', 'blur', 'sharpen'
qualityJPEG quality1-100 (default: 80)
formatOutput format'jpg', 'png', 'webp', 'avif'
alphaTransparency0-100

Cropping Modes:

  • fill - Resize and crop to exact dimensions
  • fit - Fit within dimensions, maintain aspect ratio
  • limit - Only downscale, never upscale
  • pad - Fit and add padding to match dimensions
  • scale - Resize to dimensions, ignore aspect ratio

Gravity Options:

  • center - Center of image
  • face - Detected faces
  • north, south, east, west - Directional
  • north_east, north_west, south_east, south_west - Corners

grayscale

Convert image to grayscale:

liquid
{{ image.url | grayscale }}

<img src="{{ product.image.url | grayscale }}" alt="B&W {{ product.name }}">

Image Tag Helpers

image_tag

Generate complete image tag:

liquid
{{ 'logo.png' | theme_image_url | image_tag }}
<!-- Output: <img src="/assets/.../logo.png" alt="Logo"> -->

{{ image.url | image_tag, alt: product.name, class: 'product-image' }}

{{ photo | image_tag, alt: "Team photo", width: 800, height: 600 }}

With Lazy Loading:

liquid
{{ image.url | image_tag, loading: 'lazy', class: 'lazy-image' }}

Responsive Images

Srcset Generation

Create responsive image sets:

liquid
{% assign image_400 = product.image.url | filter, width: '400px' %}
{% assign image_800 = product.image.url | filter, width: '800px' %}
{% assign image_1200 = product.image.url | filter, width: '1200px' %}

<img
  src="{{ image_800 }}"
  srcset="{{ image_400 }} 400w,
          {{ image_800 }} 800w,
          {{ image_1200 }} 1200w"
  sizes="(max-width: 600px) 400px,
         (max-width: 1200px) 800px,
         1200px"
  alt="{{ product.name }}"
  loading="lazy">

Picture Element

Art direction with different crops:

liquid
<picture>
  <source
    media="(max-width: 600px)"
    srcset="{{ image.url | filter, width: '600px', height: '400px', cropping: 'fill', gravity: 'face' }}">
  <source
    media="(max-width: 1200px)"
    srcset="{{ image.url | filter, width: '1200px', height: '600px', cropping: 'fill' }}">
  <img
    src="{{ image.url | filter, width: '1920px', height: '800px', cropping: 'fill' }}"
    alt="{{ image.title }}">
</picture>

Stylesheet and Script Tags

stylesheet_tag

Generate stylesheet link:

liquid
{{ 'theme.css' | asset_url | stylesheet_tag }}
<!-- Output: <link href="/assets/.../theme.css" rel="stylesheet"> -->

{{ 'custom.css' | asset_url | stylesheet_tag, media: 'print' }}

script_tag

Generate script tag:

liquid
{{ 'app.js' | asset_url | script_tag }}
<!-- Output: <script src="/assets/.../app.js"></script> -->

{{ 'analytics.js' | asset_url | script_tag, defer: true }}
{{ 'polyfill.js' | asset_url | script_tag, async: true }}

Practical Examples

Product Images with Variants

liquid
<div class="product-gallery">
  <!-- Main image -->
  <div class="main-image">
    {% if product.images.first %}
      {% assign main_image = product.images.first.url %}
      <img
        src="{{ main_image | filter, width: '800px', height: '800px', cropping: 'fill' }}"
        srcset="{{ main_image | filter, width: '400px', height: '400px', cropping: 'fill' }} 400w,
                {{ main_image | filter, width: '800px', height: '800px', cropping: 'fill' }} 800w,
                {{ main_image | filter, width: '1200px', height: '1200px', cropping: 'fill' }} 1200w"
        sizes="(max-width: 768px) 400px, 800px"
        alt="{{ product.name }}"
        loading="eager">
    {% endif %}
  </div>

  <!-- Thumbnails -->
  <div class="thumbnails">
    {% for image in product.images %}
      <button class="thumbnail" data-index="{{ forloop.index0 }}">
        <img
          src="{{ image.url | filter, width: '100px', height: '100px', cropping: 'fill' }}"
          alt="{{ product.name }} - Image {{ forloop.index }}"
          loading="lazy">
      </button>
    {% endfor %}
  </div>
</div>

Hero Banner with Optimizations

liquid
<section class="hero">
  {% if page.hero_image %}
    <picture>
      <!-- Mobile: Portrait crop -->
      <source
        media="(max-width: 768px)"
        srcset="{{ page.hero_image.url | filter, width: '768px', height: '1024px', cropping: 'fill', gravity: 'center', format: 'webp' }} 768w"
        type="image/webp">

      <!-- Tablet: Landscape -->
      <source
        media="(max-width: 1200px)"
        srcset="{{ page.hero_image.url | filter, width: '1200px', height: '600px', cropping: 'fill', format: 'webp' }} 1200w"
        type="image/webp">

      <!-- Desktop: Full width -->
      <source
        srcset="{{ page.hero_image.url | filter, width: '1920px', height: '800px', cropping: 'fill', format: 'webp', quality: 85 }} 1920w"
        type="image/webp">

      <!-- Fallback JPG -->
      <img
        src="{{ page.hero_image.url | filter, width: '1920px', height: '800px', cropping: 'fill', quality: 85 }}"
        alt="{{ page.hero_title }}"
        loading="eager">
    </picture>
  {% endif %}

  <div class="hero-content">
    <h1>{{ page.hero_title }}</h1>
    <p>{{ page.hero_subtitle }}</p>
  </div>
</section>

Team Member Cards

liquid
<div class="team-grid">
  {% for member in channels.team.all %}
    <div class="team-card">
      {% if member.photo %}
        <div class="photo">
          <img
            src="{{ member.photo.url | filter, width: '300px', height: '300px', cropping: 'fill', gravity: 'face' }}"
            alt="{{ member.name }}"
            loading="lazy">
        </div>
      {% else %}
        <div class="photo placeholder">
          <img src="{{ 'team-placeholder.png' | theme_image_url }}" alt="{{ member.name }}">
        </div>
      {% endif %}

      <h3>{{ member.name }}</h3>
      <p class="role">{{ member.role }}</p>
    </div>
  {% endfor %}
</div>

Background Images

liquid
<section
  class="cta-section"
  style="background-image: url('{{ page.background.url | filter, width: '1920px', quality: 75, effect: 'blur' }}')">

  <div class="cta-content">
    <h2>{{ page.cta_title }}</h2>
    <a href="{{ page.cta_link }}" class="btn">{{ page.cta_text }}</a>
  </div>
</section>

Asset Preloading

liquid
<head>
  <!-- Preload critical images -->
  <link
    rel="preload"
    as="image"
    href="{{ page.hero_image.url | filter, width: '1920px', format: 'webp' }}"
    type="image/webp">

  <!-- Preload fonts -->
  <link
    rel="preload"
    as="font"
    href="{{ 'Inter-Regular.woff2' | asset_url }}"
    type="font/woff2"
    crossorigin>

  <!-- Stylesheets -->
  {{ 'theme.css' | asset_url | stylesheet_tag }}

  <!-- Critical JS -->
  {{ 'critical.js' | asset_url | script_tag }}
</head>
liquid
<div class="image-gallery">
  {% for image in page.gallery %}
    <a
      href="{{ image.url | filter, width: '1920px', quality: 90 }}"
      class="gallery-item"
      data-lightbox="gallery">

      <img
        src="{{ image.url | filter, width: '400px', height: '300px', cropping: 'fill' }}"
        alt="{{ image.title }}"
        loading="lazy">
    </a>
  {% endfor %}
</div>

Performance Best Practices

1. Use Appropriate Image Formats

liquid
<!-- ✅ Good: Modern formats with fallback -->
<picture>
  <source srcset="{{ image.url | filter, width: '800px', format: 'webp' }}" type="image/webp">
  <source srcset="{{ image.url | filter, width: '800px', format: 'jpg' }}" type="image/jpeg">
  <img src="{{ image.url | filter, width: '800px' }}" alt="...">
</picture>

<!-- ⚠️ Acceptable for simple cases -->
<img src="{{ image.url | filter, width: '800px' }}" alt="...">

2. Optimize Quality Settings

liquid
<!-- ✅ Good: Optimized quality -->
{{ image.url | filter, width: '1200px', quality: 80 }}

<!-- ❌ Bad: Unnecessarily high quality -->
{{ image.url | filter, width: '1200px', quality: 100 }}

3. Lazy Load Below the Fold

liquid
<!-- Above the fold: Eager loading -->
<img src="{{ hero.url | filter, width: '1920px' }}" loading="eager" alt="Hero">

<!-- Below the fold: Lazy loading -->
<img src="{{ image.url | filter, width: '800px' }}" loading="lazy" alt="...">

4. Use Face Detection for People Photos

liquid
<!-- ✅ Good: Face-aware cropping -->
{{ member.photo.url | filter, width: '300px', height: '300px', cropping: 'fill', gravity: 'face' }}

<!-- ❌ Bad: Might crop out faces -->
{{ member.photo.url | filter, width: '300px', height: '300px', cropping: 'fill' }}

Next Steps

Optimize and deliver assets effectively with Nimbu's CDN filters!