Blog CMS Workflows 8 min read

Contentful Image Field Fallback in Headless CMS Setups

Handle missing Contentful image fields in React, Next.js, and Astro frontends by inserting a generated fallback URL for any unset asset reference.

Contentful image fallbackHeadless CMSMissing mediaPlaceholder image APINext.js CMS
Contentful Image Field Fallback in Headless CMS Setups

Contentful image fields return null when an entry is saved without an attached asset. A null field in a headless frontend means the img src resolves to undefined or an empty string, which produces a broken image icon and can trigger a 404 cascade if your error handler feeds back into itself.

The cleanest fix is a small helper that produces a dimension-matched fallback URL before the value ever reaches a component. fallback.pics generates SVG placeholders from URL parameters, so you get stable layout and a visible fallback without storing any extra assets in your Contentful space.

Why it happens

Contentful image fields that return null

Every Contentful content type can have an image field marked optional. Authors frequently save draft entries with all text written but no media uploaded yet. The Contentful Delivery API returns those fields as null, not as a missing key, so code that does not check for null will receive a value — it just cannot be used as an image src.

Localization makes this worse. An entry may have an image in the default locale but null for every other locale, so a content type that looked safe in development breaks on translated pages in production. Migrate carefully when adding required validation to an existing field; existing entries will fail validation until each one is updated.

The third common source is programmatic content import. When a migration script creates entries before the corresponding assets are uploaded, there is a window where the image field is null and end users may hit the page before the import completes.

Safe access

Reading Contentful asset fields without crashing

The Contentful SDK returns typed responses when you use their TypeScript client. An image field typed as `Asset | undefined` requires explicit null checking before you read `fields.file?.url`. Accessing nested properties on an undefined Asset throws at runtime.

The safest pattern is a dedicated helper that accepts the full Contentful entry, extracts the image URL if it exists, and returns a fallback URL otherwise. Centralizing this logic means you change the fallback in one place across all templates.

Implementation tsx
// lib/contentful-image.ts
import type { Asset } from 'contentful';

export function getImageSrc(
  asset: Asset | undefined | null,
  width: number,
  height: number,
  label = '',
): string {
  if (asset?.fields?.file?.url) {
    const url = asset.fields.file.url as string;
    return url.startsWith('//') ? `https:${url}` : url;
  }
  const text = label
    ? encodeURIComponent(label)
    : encodeURIComponent(`${width}×${height}`);
  return `https://fallback.pics/api/v1/${width}x${height}?text=${text}`;
}

Fallback URL

Generate dimension-matched placeholders from the API

Pass the same dimensions you use for the final image slot. If your blog hero is 1200×630, your fallback should also be 1200×630 so the page does not reflow when the real image loads. This keeps Cumulative Layout Shift at zero even during the loading window.

Use the text parameter to show something more informative than a plain color block. A short label like the post category or 'Image coming soon' gives authors visual feedback in the CMS preview that the field still needs filling.

Implementation text
// Blog featured image
https://fallback.pics/api/v1/1200x630?text=Blog+Post

// Product card thumbnail
https://fallback.pics/api/v1/400x300?text=Product+Image

// Author avatar
https://fallback.pics/api/v1/avatar/80?text=AB

// Wide banner
https://fallback.pics/api/v1/banner/1200x400?text=Hero+Banner

Draft previews

Preview mode exposes more null fields than production

Contentful preview mode uses the Preview API, which returns unpublished entries. An author may publish text-only updates, leaving the image field empty in the draft state. If your Next.js preview route does not apply the same fallback logic as the production route, you get broken images only during editorial review — which delays publishing and trains authors to ignore broken previews.

Apply the same `getImageSrc` helper in both the production and preview data-fetching paths. The generated fallback URL is cacheable and deterministic, so it appears instantly without hitting Contentful's image transform API.

Avatars

Author and profile image fallbacks

Author profiles often have an optional headshot field. When that field is null, the avatar slot collapses or shows a broken icon. The avatar route accepts a text parameter for initials, which makes the fallback feel intentional rather than broken.

Generate initials from the author name field, which is always present. A two-letter uppercase initial works reliably across all avatar sizes.

Implementation tsx
function authorAvatarSrc(author: ContentfulAuthor): string {
  if (author.fields.headshot?.fields.file?.url) {
    const url = author.fields.headshot.fields.file.url as string;
    return url.startsWith('//') ? `https:${url}` : url;
  }
  const name = author.fields.name as string;
  const initials = name
    .split(' ')
    .map((w) => w[0])
    .slice(0, 2)
    .join('')
    .toUpperCase();
  return `https://fallback.pics/api/v1/avatar/80?text=${initials}`;
}

Next.js

Use with Next.js Image and the Contentful domain

Next.js `<Image>` requires every external image domain in `next.config.js`. Add both `images.ctfassets.net` for Contentful and `fallback.pics` to `remotePatterns`. Without both entries, Next.js will throw a runtime error when the fallback URL is used as the src.

Set `width` and `height` props on the Next.js Image component equal to the fallback.pics dimensions. This eliminates CLS regardless of whether the displayed src is the Contentful asset or the fallback.

Implementation tsx
// next.config.js
module.exports = {
  images: {
    remotePatterns: [
      { protocol: 'https', hostname: 'images.ctfassets.net' },
      { protocol: 'https', hostname: 'fallback.pics' },
    ],
  },
};

// components/ContentfulImage.tsx
import Image from 'next/image';
import { getImageSrc } from '../lib/contentful-image';

export function ContentfulImage({ asset, width, height, alt, label }) {
  return (
    <Image
      src={getImageSrc(asset, width, height, label)}
      width={width}
      height={height}
      alt={alt}
    />
  );
}

Resources

Further reading

Check the fallback.pics docs for the full list of URL parameters including custom colors, formats, and skeleton states. If you are setting up fallbacks across multiple CMS platforms, the general CMS placeholder pattern guide covers the common patterns in one place.

Implementation text
https://fallback.pics/docs/
https://fallback.pics/placeholder-image-api/
https://fallback.pics/blog/placeholder-images-cms-previews-missing-media/
https://fallback.pics/blog/sanity-cms-image-fallback/

Key takeaways

What to standardize before shipping

  • Contentful image fields return null for unpublished, localized, or import-incomplete entries — always guard before using as src.
  • Centralize fallback logic in a helper that accepts an Asset and returns a fallback.pics URL for null fields.
  • Match the placeholder dimensions to the final slot dimensions to prevent layout shift.
  • Use the avatar route with initials for author headshot fallbacks.
  • Add fallback.pics to Next.js remotePatterns alongside ctfassets.net to avoid runtime errors.

Production fallback layer

Use fallback.pics anywhere an image URL is accepted.

Start with one deterministic URL and standardize fallback behavior across your design system.

Read the docs