Essentials

Image Optimization

Optimize images with Contentstack's Image Delivery API and @nuxt/image integration for responsive, fast-loading images with advanced transformations.

Nuxt Contentstack provides powerful image optimization through seamless integration with @nuxt/image and Contentstack's Image Delivery API, enabling responsive images, automatic format conversion, and advanced transformations.

Explore Image Delivery API: Learn about all available image transformations, parameters, and capabilities in the Contentstack Image Delivery API documentation. Discover advanced features like overlays, filters, canvas manipulation, and more.

Overview

Image optimization features include:

  • @nuxt/image Integration: Native Nuxt image optimization
  • Contentstack Provider: Custom provider for Image Delivery API
  • Responsive Images: Automatic sizing and density handling
  • Format Optimization: WebP, AVIF conversion with fallbacks
  • Advanced Transformations: Resize, crop, effects, and overlays
  • Lazy Loading: Optimized loading strategies

Setup

Note: To use Contentstack's image optimization features, you need @nuxt/image version 2.0.0 or higher. The module can run without it if you don't need image optimization.

1. Install @nuxt/image

Install @nuxt/image if you want to use image optimization features:

npm install @nuxt/image@^2.0.0

2. Configure Modules

nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    'nuxt-contentstack',
    '@nuxt/image'
  ]
})
Auto-registration: When @nuxt/image is detected, the module automatically registers the Contentstack image provider. No manual provider configuration is needed. To set it as the default provider (so you don't need provider="contentstack" on every component), add image: { provider: "contentstack" } to your config.

## Basic Usage

### NuxtImg Component

```vue [components/OptimizedImage.vue]
<template>
  <NuxtImg
    :src="image.url"
    :alt="image.title"
    width="800"
    height="400"
    provider="contentstack"
  />
</template>

<script setup>
defineProps<{
  image: {
    url: string
    title: string
  }
}>()
</script>

NuxtPicture for Art Direction

components/ResponsiveImage.vue
<template>
  <NuxtPicture
    :src="hero.image.url"
    :imgAttrs="{ alt: hero.image.title }"
    sizes="100vw md:50vw lg:33vw"
    densities="1x 2x"
    provider="contentstack"
  />
</template>

Advanced Transformations

Quality and Format Optimization

components/QualityOptimized.vue
<template>
  <NuxtImg
    :src="image.url"
    :alt="image.title"
    width="600"
    height="400"
    :modifiers="{
      auto: 'webp,compress',
      quality: 85,
      format: 'webp'
    }"
    provider="contentstack"
  />
</template>

Cropping and Fitting

components/CroppedImage.vue
<template>
  <NuxtImg
    :src="gallery.image.url"
    width="300"
    height="300"
    :modifiers="{
      fit: 'crop',
      crop: 'center',
      quality: 90
    }"
    provider="contentstack"
  />
</template>

Visual Effects

components/StylizedImage.vue
<template>
  <NuxtImg
    :src="artistic.image.url"
    width="500"
    height="300"
    :modifiers="{
      blur: 2,
      brightness: 110,
      contrast: 120,
      saturation: 130,
      quality: 80
    }"
    provider="contentstack"
  />
</template>

useImageTransform Composable

For programmatic image transformations and dynamic modifications:

Basic Usage

The composable returns four values:

  • transformedUrl - Computed URL with all transformations applied
  • transformOptions - Readonly ref of current transform options
  • updateTransform - Function to merge new options into current transforms
  • resetTransform - Function to clear all transformations
components/DynamicImage.vue
<script setup>
const props = defineProps<{
  imageUrl: string
  size: 'small' | 'medium' | 'large'
}>()

const sizeMap = {
  small: { width: 300, height: 200 },
  medium: { width: 600, height: 400 },
  large: { width: 1200, height: 800 }
}

const { transformedUrl, updateTransform, resetTransform } = useImageTransform(
  props.imageUrl,
  {
    ...sizeMap[props.size],
    quality: 85,
    format: 'webp',
    auto: 'webp,compress'
  }
)

// Dynamically update transforms
const switchSize = (newSize: 'small' | 'medium' | 'large') => {
  updateTransform(sizeMap[newSize])
}
</script>

<template>
  <img
    :src="transformedUrl"
    :alt="alt"
    class="responsive-image"
  />
</template>

Advanced Features

Overlay Images

composables/useWatermark.ts
export const useWatermarkImage = (baseImageUrl: string, watermarkUrl: string) => {
  const { transformedUrl } = useImageTransform(baseImageUrl, {
    width: 800,
    height: 600,
    quality: 85,
    overlay: {
      relativeURL: watermarkUrl,
      align: 'bottom',        // 'top' | 'bottom' | 'left' | 'right' | 'middle' | 'center'
      repeat: 'both',         // 'x' | 'y' | 'both' (optional)
      width: '20p',           // 20% of base image
      height: 50,             // pixels
      pad: 10                 // padding from edge
    }
  })

  return { watermarkedUrl: transformedUrl }
}

Smart Cropping

components/SmartCrop.vue
<script setup>
const props = defineProps<{
  image: {
    url: string
    title: string
    focalPoint?: {
      x: number
      y: number
    }
  }
}>()

const { transformedUrl } = useImageTransform(props.image.url, {
  width: 400,
  height: 300,
  fit: 'crop',
  crop: props.image.focalPoint 
    ? `${props.image.focalPoint.x},${props.image.focalPoint.y}`
    : 'smart'
})
</script>

<template>
  <img 
    :src="transformedUrl" 
    :alt="image.title"
    class="smart-cropped"
  />
</template>

Progressive Enhancement

Load a low-quality placeholder first, then swap to high-quality:

components/ProgressiveImage.vue
<script setup>
const props = defineProps<{
  imageUrl: string
  alt: string
}>()

// Low quality placeholder
const { transformedUrl: placeholder } = useImageTransform(props.imageUrl, {
  width: 50,
  quality: 20,
  blur: 5
})

// High quality image
const { transformedUrl: highQuality } = useImageTransform(props.imageUrl, {
  width: 800,
  quality: 85,
  format: 'webp'
})

const isLoaded = ref(false)
</script>

<template>
  <div class="progressive-image">
    <img v-show="!isLoaded" :src="placeholder" :alt="alt" class="placeholder" />
    <img :src="highQuality" :alt="alt" @load="isLoaded = true" />
  </div>
</template>

Responsive Images

Use NuxtPicture for responsive images with breakpoint-based sizing:

components/ResponsiveHero.vue
<template>
  <NuxtPicture
    :src="hero.image.url"
    :imgAttrs="{ alt: hero.image.title }"
    :modifiers="{ quality: 85, auto: 'webp,compress' }"
    sizes="100vw sm:90vw md:80vw lg:1200px"
    densities="1x 2x"
    provider="contentstack"
  />
</template>

Art Direction

components/ArtDirectedImage.vue
<template>
  <picture>
    <!-- Desktop version (landscape) -->
    <source
      media="(min-width: 768px)"
      :srcset="desktopSrcset"
      type="image/webp"
    />
    
    <!-- Mobile version (portrait crop) -->
    <source
      :srcset="mobileSrcset"
      type="image/webp"
    />
    
    <!-- Fallback -->
    <img :src="fallbackSrc" :alt="image.title" />
  </picture>
</template>

<script setup>
const props = defineProps<{
  image: { url: string; title: string }
}>()

// Desktop landscape
const { transformedUrl: desktopSrcset } = useImageTransform(props.image.url, {
  width: 1200,
  height: 600,
  fit: 'crop',
  crop: 'center'
})

// Mobile portrait
const { transformedUrl: mobileSrcset } = useImageTransform(props.image.url, {
  width: 400,
  height: 600,
  fit: 'crop',
  crop: 'top'
})

// Fallback
const { transformedUrl: fallbackSrc } = useImageTransform(props.image.url, {
  width: 800,
  height: 400
})
</script>

Performance Optimization

Lazy Loading

components/LazyImage.vue
<template>
  <NuxtImg
    :src="image.url"
    :alt="image.title"
    loading="lazy"
    width="600"
    height="400"
    :modifiers="{ quality: 80 }"
    provider="contentstack"
  />
</template>

Preloading Critical Images

pages/index.vue
<script setup>
// Preload hero image
const { data: page } = await useGetEntryByUrl({
  contentTypeUid: 'landing_page',
  url: '/'
})

if (page.value?.hero?.image) {
  const { transformedUrl } = useImageTransform(page.value.hero.image.url, {
    width: 1200,
    height: 600,
    quality: 85
  })
  
  // Preload the hero image
  useHead({
    link: [
      {
        rel: 'preload',
        href: transformedUrl,
        as: 'image'
      }
    ]
  })
}
</script>

Multiple Image Sizes

Generate multiple sizes for galleries and lightboxes:

components/OptimizedGallery.vue
<script setup>
const props = defineProps<{
  images: Array<{ url: string; title: string }>
}>()

const optimizedImages = computed(() => {
  return props.images.map(image => ({
    ...image,
    thumbnail: useImageTransform(image.url, { width: 300, height: 200, quality: 75 }).transformedUrl,
    medium: useImageTransform(image.url, { width: 800, height: 600, quality: 85 }).transformedUrl,
    large: useImageTransform(image.url, { width: 1600, height: 1200, quality: 90 }).transformedUrl
  }))
})
</script>

<template>
  <div class="gallery">
    <img 
      v-for="image in optimizedImages" 
      :key="image.url"
      :src="image.thumbnail"
      :alt="image.title"
      loading="lazy"
    />
  </div>
</template>

Error Handling

Handle image loading errors and states:

components/SafeImage.vue
<script setup>
const props = defineProps<{
  src: string
  alt: string
  fallback?: string
}>()

const imageSrc = ref(props.src)
const hasError = ref(false)

const handleError = () => {
  if (!hasError.value && props.fallback) {
    hasError.value = true
    imageSrc.value = props.fallback
  }
}
</script>

<template>
  <div v-if="hasError && !fallback" class="error-state">
    Failed to load image
  </div>
  <img v-else :src="imageSrc" :alt="alt" @error="handleError" />
</template>

Available Transformations

Provider Defaults: When using the Contentstack @nuxt/image provider, images automatically get auto: 'webp,compress' and quality: 80 applied by default. You can override these by specifying your own values.

Dimension Control

ParameterDescriptionExample
widthSet image widthwidth: 800
heightSet image heightheight: 600
dprDevice pixel ratiodpr: 2
disableDisable upscalingdisable: 'upscale'

Quality and Format

ParameterDescriptionExample
qualityImage quality (1-100)quality: 85
formatOutput format (webp, png, jpg, jpeg, gif, auto)format: 'webp'
autoAuto optimization (webp or webp,compress)auto: 'webp,compress'

Cropping and Fitting

ParameterDescriptionExample
fitResize behavior (bounds or crop)fit: 'crop'
cropCrop positioncrop: 'center'
trimRemove edges (single value or array)trim: 5 or trim: [5, 10, 5, 10]
orientImage orientation (default, 1-8)orient: '6'
padPadding around image (single value or array)pad: 10 or pad: [5, 10, 5, 10]
bgBackground color (hex)bg: 'FF0000'

Effects

ParameterDescriptionExample
blurBlur amountblur: 5
brightnessBrightness adjustmentbrightness: 10
contrastContrast adjustmentcontrast: 20
saturationSaturation adjustmentsaturation: 15
sharpenSharpen with amount, radius, thresholdsharpen: { amount: 5, radius: 1, threshold: 0 }
frameExtract specific frame (for animated images)frame: 1
resizeFilterResize algorithm (nearest, bilinear, bicubic, lanczos2, lanczos3)resizeFilter: 'lanczos3'

Overlay

ParameterDescriptionExample
overlay.relativeURLURL of overlay image (required)overlay: { relativeURL: '/watermark.png' }
overlay.alignOverlay alignment (top, bottom, left, right, middle, center)overlay: { relativeURL: '...', align: 'bottom' }
overlay.repeatRepeat overlay (x, y, both)overlay: { relativeURL: '...', repeat: 'both' }
overlay.widthOverlay width (pixels or percentage string)overlay: { relativeURL: '...', width: '20p' }
overlay.heightOverlay height (pixels or percentage string)overlay: { relativeURL: '...', height: 50 }
overlay.padPadding from edgeoverlay: { relativeURL: '...', pad: 10 }

Next Steps

Composables

Learn how to fetch and use images with Contentstack composables.