Skip to main content

Use CMS photos

Map CMS assets to PhotoItem and store dimensions before rendering.

Map CMS-specific fields once at the data boundary. The component should receive one valid PhotoItem collection with stable IDs and real dimensions.

Map assets that include dimensions

app/pages/gallery.vue
<script setup lang="ts">
import type { PhotoItem } from '@lupinum/nuxt-photo/app'

type Asset = {
  key: string
  url: string
  width: number
  height: number
  alternativeText?: string
  title?: string
}

const { data: assets } = await useFetch<Asset[]>('/api/photos')

const photos = computed<PhotoItem[]>(() =>
  (assets.value ?? []).map((asset) => ({
    id: asset.key,
    src: asset.url,
    width: asset.width,
    height: asset.height,
    alt: asset.alternativeText,
    caption: asset.title,
  })),
)
</script>

<template>
  <PhotoAlbum :photos="photos" layout="rows" />
</template>

Use the CMS asset ID instead of the array index. Preserve the original pixel dimensions even when the image service returns a smaller display URL.

When the CMS has no dimensions

Calculate width and height during upload or trusted server-side ingestion, then store them with the asset. Do not download and measure images in the browser. Browser measurement makes layout depend on the network request Nuxt Photo is designed to avoid.

For an upload pipeline that already uses sharp:

server/utils/read-image-dimensions.ts
import sharp from 'sharp'

export async function readImageDimensions(file: Buffer) {
  const metadata = await sharp(file).metadata()

  if (!metadata.width || !metadata.height) {
    throw new Error('The uploaded image has no readable width or height.')
  }

  return {
    width: metadata.width,
    height: metadata.height,
  }
}

Call this function before the upload record is written. Save both values in the CMS or application database. If an image provider already exposes metadata, use that response instead of downloading the asset again.

For existing assets, run a one-time server-side backfill from a trusted source. Do not fetch arbitrary user-controlled URLs without the normal server-side request protections.

Keep URLs stable during SSR

Generate signed or expiring URLs before rendering. Store the final URL on the photo for that request. Calling Date.now() or using random state inside an image adapter can give the server and browser different markup.

Handle invalid records deliberately

The default validation policy throws when a photo is missing required data or uses a duplicate ID. Keep that behavior while integrating a new CMS.

Use validation="drop" only when the page may continue without invalid records, and report the emitted event:

vue
<PhotoAlbum :photos="photos" validation="drop" @invalid-photos="reportInvalidPhotos" />

Check one portrait, one landscape, a duplicate ID, and a missing dimension before treating the CMS mapping as complete.