Image Optimization
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.
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/imageversion 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
export default defineNuxtConfig({
modules: [
'nuxt-contentstack',
'@nuxt/image'
]
})
@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
<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
<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
<template>
<NuxtImg
:src="gallery.image.url"
width="300"
height="300"
:modifiers="{
fit: 'crop',
crop: 'center',
quality: 90
}"
provider="contentstack"
/>
</template>
Visual Effects
<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 appliedtransformOptions- Readonly ref of current transform optionsupdateTransform- Function to merge new options into current transformsresetTransform- Function to clear all transformations
<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
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
<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:
<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:
<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
<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
<template>
<NuxtImg
:src="image.url"
:alt="image.title"
loading="lazy"
width="600"
height="400"
:modifiers="{ quality: 80 }"
provider="contentstack"
/>
</template>
Preloading Critical Images
<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:
<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:
<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
@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
| Parameter | Description | Example |
|---|---|---|
width | Set image width | width: 800 |
height | Set image height | height: 600 |
dpr | Device pixel ratio | dpr: 2 |
disable | Disable upscaling | disable: 'upscale' |
Quality and Format
| Parameter | Description | Example |
|---|---|---|
quality | Image quality (1-100) | quality: 85 |
format | Output format (webp, png, jpg, jpeg, gif, auto) | format: 'webp' |
auto | Auto optimization (webp or webp,compress) | auto: 'webp,compress' |
Cropping and Fitting
| Parameter | Description | Example |
|---|---|---|
fit | Resize behavior (bounds or crop) | fit: 'crop' |
crop | Crop position | crop: 'center' |
trim | Remove edges (single value or array) | trim: 5 or trim: [5, 10, 5, 10] |
orient | Image orientation (default, 1-8) | orient: '6' |
pad | Padding around image (single value or array) | pad: 10 or pad: [5, 10, 5, 10] |
bg | Background color (hex) | bg: 'FF0000' |
Effects
| Parameter | Description | Example |
|---|---|---|
blur | Blur amount | blur: 5 |
brightness | Brightness adjustment | brightness: 10 |
contrast | Contrast adjustment | contrast: 20 |
saturation | Saturation adjustment | saturation: 15 |
sharpen | Sharpen with amount, radius, threshold | sharpen: { amount: 5, radius: 1, threshold: 0 } |
frame | Extract specific frame (for animated images) | frame: 1 |
resizeFilter | Resize algorithm (nearest, bilinear, bicubic, lanczos2, lanczos3) | resizeFilter: 'lanczos3' |
Overlay
| Parameter | Description | Example |
|---|---|---|
overlay.relativeURL | URL of overlay image (required) | overlay: { relativeURL: '/watermark.png' } |
overlay.align | Overlay alignment (top, bottom, left, right, middle, center) | overlay: { relativeURL: '...', align: 'bottom' } |
overlay.repeat | Repeat overlay (x, y, both) | overlay: { relativeURL: '...', repeat: 'both' } |
overlay.width | Overlay width (pixels or percentage string) | overlay: { relativeURL: '...', width: '20p' } |
overlay.height | Overlay height (pixels or percentage string) | overlay: { relativeURL: '...', height: 50 } |
overlay.pad | Padding from edge | overlay: { relativeURL: '...', pad: 10 } |
Next Steps
Components
Learn about the Vue components provided by Nuxt Contentstack, including the powerful ContentstackModularBlocks component for dynamic content rendering.
Personalization
Deliver personalized content experiences with Contentstack Personalize integration, including user attributes, events, and variant-based content delivery.