Skip to main content

Overview

Responsive images ensure your website delivers optimally-sized images to every device, reducing page load times and bandwidth usage. The ButterCMS Image API makes this easy through URL-based transformations.

Why responsive images matter

  • Performance: Smaller images load faster on mobile devices
  • Bandwidth: Save data for users on limited connections
  • SEO: Google rewards fast-loading pages
  • User Experience: Images display correctly across all screen sizes

Basic implementation

Using srcset

The srcset attribute lets browsers choose the best image size:
<img
  src="https://cdn.buttercms.com/resize=width:800/abc123"
  srcset="
    https://cdn.buttercms.com/resize=width:400/abc123 400w,
    https://cdn.buttercms.com/resize=width:800/abc123 800w,
    https://cdn.buttercms.com/resize=width:1200/abc123 1200w,
    https://cdn.buttercms.com/resize=width:1600/abc123 1600w
  "
  sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 800px"
  alt="Responsive image example"
/>

Understanding srcset and sizes

AttributePurpose
srcFallback for browsers that don’t support srcset
srcsetList of available image URLs with width descriptors
sizesMedia conditions mapping viewport widths to image widths

Common patterns

Full-width hero image

<img
  src="https://cdn.buttercms.com/resize=width:1200/abc123"
  srcset="
    https://cdn.buttercms.com/resize=width:640/abc123 640w,
    https://cdn.buttercms.com/resize=width:960/abc123 960w,
    https://cdn.buttercms.com/resize=width:1280/abc123 1280w,
    https://cdn.buttercms.com/resize=width:1920/abc123 1920w
  "
  sizes="100vw"
  alt="Hero image"
/>

Blog Post featured image

<img
  src="https://cdn.buttercms.com/resize=width:800/abc123"
  srcset="
    https://cdn.buttercms.com/resize=width:400/abc123 400w,
    https://cdn.buttercms.com/resize=width:600/abc123 600w,
    https://cdn.buttercms.com/resize=width:800/abc123 800w,
    https://cdn.buttercms.com/resize=width:1200/abc123 1200w
  "
  sizes="(max-width: 768px) 100vw, 800px"
  alt="Blog featured image"
/>

Card grid image

<img
  src="https://cdn.buttercms.com/resize=width:400,height:300,fit:crop/abc123"
  srcset="
    https://cdn.buttercms.com/resize=width:200,height:150,fit:crop/abc123 200w,
    https://cdn.buttercms.com/resize=width:400,height:300,fit:crop/abc123 400w,
    https://cdn.buttercms.com/resize=width:600,height:450,fit:crop/abc123 600w
  "
  sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 400px"
  alt="Card image"
/>

Using the picture element

For art direction (different crops for different devices):
<picture>
  <!-- Mobile: square crop -->
  <source
    media="(max-width: 600px)"
    srcset="https://cdn.buttercms.com/resize=width:600,height:600,fit:crop/abc123"
  />
  <!-- Tablet: 16:9 crop -->
  <source
    media="(max-width: 1200px)"
    srcset="https://cdn.buttercms.com/resize=width:1200,height:675,fit:crop/abc123"
  />
  <!-- Desktop: wide crop -->
  <img
    src="https://cdn.buttercms.com/resize=width:1920,height:800,fit:crop/abc123"
    alt="Art directed image"
  />
</picture>

Framework examples

React component

function ResponsiveImage({ src, alt, sizes = '100vw' }) {
  const fileId = src.split('/').pop();
  const baseUrl = 'https://cdn.buttercms.com';

  const widths = [400, 800, 1200, 1600];

  const srcSet = widths
    .map(w => `${baseUrl}/resize=width:${w}/${fileId} ${w}w`)
    .join(', ');

  return (
    <img
      src={`${baseUrl}/resize=width:800/${fileId}`}
      srcSet={srcSet}
      sizes={sizes}
      alt={alt}
      loading="lazy"
    />
  );
}

// Usage
<ResponsiveImage
  src="https://cdn.buttercms.com/abc123"
  alt="My image"
  sizes="(max-width: 768px) 100vw, 800px"
/>

Vue component

<template>
  <img
    :src="defaultSrc"
    :srcset="srcSet"
    :sizes="sizes"
    :alt="alt"
    loading="lazy"
  />
</template>

<script setup>
import { computed } from 'vue';

const props = defineProps({
  src: String,
  alt: String,
  sizes: { type: String, default: '100vw' },
  widths: { type: Array, default: () => [400, 800, 1200, 1600] }
});

const fileId = computed(() => props.src.split('/').pop());
const baseUrl = 'https://cdn.buttercms.com';

const defaultSrc = computed(() =>
  `${baseUrl}/resize=width:800/${fileId.value}`
);

const srcSet = computed(() =>
  props.widths
    .map(w => `${baseUrl}/resize=width:${w}/${fileId.value} ${w}w`)
    .join(', ')
);
</script>

Next.js with next/image

import Image from 'next/image';

// Custom loader for ButterCMS
const butterLoader = ({ src, width, quality }) => {
  const fileId = src.split('/').pop();
  const q = quality || 75;
  return `https://cdn.buttercms.com/resize=width:${width}/quality=value:${q}/${fileId}`;
};

export default function ButterImage({ src, alt, ...props }) {
  return (
    <Image
      loader={butterLoader}
      src={src}
      alt={alt}
      {...props}
    />
  );
}

// Usage
<ButterImage
  src="https://cdn.buttercms.com/abc123"
  alt="My image"
  width={800}
  height={600}
  sizes="(max-width: 768px) 100vw, 800px"
/>

WebP with fallback

Serve WebP to supported browsers with JPEG fallback:
<picture>
  <source
    type="image/webp"
    srcset="
      https://cdn.buttercms.com/resize=width:400/output=format:webp/abc123 400w,
      https://cdn.buttercms.com/resize=width:800/output=format:webp/abc123 800w,
      https://cdn.buttercms.com/resize=width:1200/output=format:webp/abc123 1200w
    "
    sizes="(max-width: 768px) 100vw, 800px"
  />
  <img
    src="https://cdn.buttercms.com/resize=width:800/abc123"
    srcset="
      https://cdn.buttercms.com/resize=width:400/abc123 400w,
      https://cdn.buttercms.com/resize=width:800/abc123 800w,
      https://cdn.buttercms.com/resize=width:1200/abc123 1200w
    "
    sizes="(max-width: 768px) 100vw, 800px"
    alt="WebP with fallback"
  />
</picture>

Helper functions

JavaScript utility

class ButterImage {
  constructor(url) {
    this.fileId = url.split('/').pop();
    this.baseUrl = 'https://cdn.buttercms.com';
  }

  resize(width, height, fit = 'clip') {
    let params = `width:${width}`;
    if (height) params += `,height:${height}`;
    if (fit !== 'clip') params += `,fit:${fit}`;
    return `${this.baseUrl}/resize=${params}/${this.fileId}`;
  }

  generateSrcSet(widths, options = {}) {
    const { height, fit, format } = options;

    return widths.map(width => {
      let transforms = [];

      // Resize
      let resizeParams = `width:${width}`;
      if (height) {
        const scaledHeight = Math.round(height * (width / widths[widths.length - 1]));
        resizeParams += `,height:${scaledHeight}`;
      }
      if (fit) resizeParams += `,fit:${fit}`;
      transforms.push(`resize=${resizeParams}`);

      // Format conversion
      if (format) {
        transforms.push(`output=format:${format}`);
      }

      const url = `${this.baseUrl}/${transforms.join('/')}/${this.fileId}`;
      return `${url} ${width}w`;
    }).join(', ');
  }
}

// Usage
const img = new ButterImage('https://cdn.buttercms.com/abc123');

console.log(img.resize(800));
// https://cdn.buttercms.com/resize=width:800/abc123

console.log(img.generateSrcSet([400, 800, 1200], { format: 'webp' }));
// https://cdn.buttercms.com/resize=width:400/output=format:webp/abc123 400w, ...

Best practices

Use Appropriate Sizes

Don’t serve 4K images for thumbnails. Match image sizes to display sizes.

Lazy Loading

Add loading="lazy" for images below the fold.

WebP When Possible

WebP offers 25-35% smaller files than JPEG at similar quality.

Test Performance

Use Lighthouse or WebPageTest to verify image optimization.
Device TypeTypical WidthSuggested srcset widths
Mobile320-480px400w, 600w
Tablet768-1024px800w, 1000w
Desktop1200-1920px1200w, 1600w, 1920w
Large Display2560px+2400w

Next steps

Image Transformations

All transformation options

Security

Secure your images

Performance

CDN optimization

Media Field

Working with media fields