Posted by

Faster Images in HubSpot Templates: srcset, Lazy Loading and Layout Shift

Images are usually the heaviest part of a HubSpot page. Three habits fix most image problems: serve the right size, load off-screen images late, and reserve space so the page does not jump. This guide shows how to apply them in custom templates and modules.

1. Reserve space with width and height

Browsers use the width and height attributes to compute an aspect ratio before the image downloads. Without them the page reflows when the image arrives, which hurts Cumulative Layout Shift (CLS).

<img src="https://example.com/hero.jpg" alt="Team at work" width="1200" height="675">

Pair it with CSS so the image stays fluid:

img { max-width: 100%; height: auto; }

2. Serve responsive sizes with srcset

srcset and sizes let the browser choose the smallest file that still looks sharp on the visitor's screen.

<img
  src="image-800.jpg"
  srcset="image-400.jpg 400w, image-800.jpg 800w, image-1200.jpg 1200w"
  sizes="(min-width: 960px) 50vw, 100vw"
  alt="Product dashboard"
  width="1200" height="800">

HubSpot's file CDN can resize images through the HubL resize_image_url function, so you do not need to upload every size by hand. Check the current HubL reference for its exact parameters before relying on it:

{% set img = module.image.src %}
<img src="{{ resize_image_url(img, 800) }}"
     srcset="{{ resize_image_url(img, 400) }} 400w,
             {{ resize_image_url(img, 800) }} 800w,
             {{ resize_image_url(img, 1200) }} 1200w"
     sizes="(min-width: 960px) 50vw, 100vw"
     alt="{{ module.image.alt }}"
     width="{{ module.image.width }}" height="{{ module.image.height }}">

3. Lazy-load below-the-fold images

Add loading="lazy" so the browser delays images until the visitor scrolls near them.

<img src="card.jpg" alt="Case study" width="600" height="400" loading="lazy">

Do not lazy-load your hero image. The largest visible image is usually the Largest Contentful Paint (LCP) element, and delaying it makes LCP worse. For that one image, use fetchpriority="high" instead:

<img src="hero.jpg" alt="..." width="1600" height="800" fetchpriority="high">

4. Make it a module option

If editors place images through your own module, expose the choice rather than hardcoding it. A simple boolean field in fields.json works well:

{
  "name": "eager_load",
  "label": "Load immediately (use for the top image only)",
  "type": "boolean",
  "default": false
}
<img src="{{ module.image.src }}" alt="{{ module.image.alt }}"
     {% if module.eager_load %}fetchpriority="high"{% else %}loading="lazy"{% endif %}>

HubSpot's built-in image module also offers its own lazy-loading setting, so for simple cases you can rely on it instead of writing markup.

5. Pick the right format and test

  • Use WebP or AVIF where your audience's browsers support them; keep JPEG or PNG as fallback if needed.
  • Compress before upload and avoid uploading 4000px photos for 600px slots.
  • Always write meaningful alt text; leave it empty only for purely decorative images.
  • Measure with PageSpeed Insights or Lighthouse before and after, and watch LCP and CLS.

Checklist

  1. Every img has width, height and alt.
  2. Large images use srcset and sizes.
  3. Below-the-fold images use loading="lazy".
  4. The hero image is not lazy-loaded.
  5. Results confirmed in Lighthouse.

Sources: MDN Web Docs (responsive images, loading attribute, fetchpriority); web.dev (Core Web Vitals, optimizing LCP and CLS); HubSpot Developer Docs (HubL functions, resize_image_url, image module).