Blog Implementation Guides 7 min read

Qwik Image Placeholders for Resumable Apps

Handle missing and broken images in Qwik using QwikImg, useSignal, and fallback.pics placeholder URLs with server-side resolution for resumable page loads.

qwik image placeholderqwik image componentqwik resumabilityplaceholder imageqwik
Qwik Image Placeholders for Resumable Apps

Qwik's resumability model serializes component state at server render time and resumes it in the browser without re-running component code. Image fallback patterns need to fit this model — state mutations on the client must be resumable from the serialized snapshot.

For Qwik City routes, resolving fallback image URLs in routeLoader functions is both the simplest and most SEO-friendly approach, covering the null-field case with zero client-side JavaScript.

Qwik context

Why image fallback needs to fit Qwik's resumability model

In a traditional React app, an onerror handler is wired up as part of the initial hydration pass. In Qwik, component code does not run at page load — it is downloaded and executed lazily when the user interacts.

For image elements, a plain string onerror attribute in serialized HTML fires immediately on image load failure without waiting for Qwik to activate the component. The img element's onerror is a native browser event that does not need Qwik's runtime.

For state-based fallback patterns — where a signal stores the current src — you need useSignal and Qwik's event handling so the state update is captured in the resumable snapshot.

routeLoader

Resolve fallback URLs in routeLoader$

For Qwik City pages and layouts, routeLoader$ runs on the server. Resolve missing image fields here so the serialized HTML already contains the correct fallback URL.

This approach works well with Qwik's resumability because the image src is static — it never needs updating in the browser unless the original URL returns a runtime 404.

Implementation tsx
// src/routes/product/[id]/index.tsx
export const useProductData = routeLoader$(async ({ params }) => {
  const product = await db.product.findById(params.id);
  return {
    ...product,
    image: product.image
      ?? `https://fallback.pics/api/v1/800x800?text=${encodeURIComponent(product.name)}`,
  };
});

Signal pattern

Client-side fallback with useSignal for runtime failures

For images whose URLs exist in the database but may return 404 from a CDN (because the file was deleted, moved, or the signed URL expired), add a client-side fallback using useSignal and an onError$ handler.

Qwik serializes the signal value into the HTML snapshot. When onerror fires, Qwik downloads the minimal code needed to update the signal and re-render only the affected DOM node.

Implementation tsx
// components/ProductImage.tsx
import { component$, useSignal, $ } from '@builder.io/qwik';

interface Props { src: string; name: string; width: number; height: number; }

export const ProductImage = component$((props: Props) => {
  const fallback =
    `https://fallback.pics/api/v1/${props.width}x${props.height}?text=${encodeURIComponent(props.name)}`;
  const src = useSignal(props.src);

  const onError = $(() => {
    if (src.value !== fallback) src.value = fallback;
  });

  return (
    <img
      src={src.value}
      alt={props.name}
      width={props.width}
      height={props.height}
      onError$={onError}
    />
  );
});

Inline fallback

Inline onError$ for static pages without reactive state

For static content pages, documentation, or places where you do not need the fallback src tracked in component state, a plain inline onError$ attribute is simpler and adds no JavaScript payload.

The native onerror fires without Qwik's runtime because it is a standard HTML event attribute — the lowest-overhead approach for pages where image failures are rare.

Implementation text
{/* In a Qwik component JSX */}
<img
  src={product.image}
  alt={product.name}
  width={800}
  height={800}
  onError$={(ev) => {
    const img = ev.target as HTMLImageElement;
    const fallback = 'https://fallback.pics/api/v1/800x800?text=Not+Found';
    if (img.src !== fallback) img.src = fallback;
  }}
/>

OG meta

Set og:image in Qwik City head

Qwik City's useDocumentHead hook lets you set meta tags from route data. Use the loader data to set a valid og:image URL, including the fallback when the content field is empty.

Implementation tsx
export default component$(() => {
  const product = useProductData();
  useDocumentHead(() => ({
    meta: [
      { property: 'og:image', content: product.value.image },
      { property: 'og:image:width', content: '800' },
      { property: 'og:image:height', content: '800' },
    ],
  }));
  return <div>...</div>;
});

Tradeoffs

routeLoader vs signal vs inline onError

The right choice depends on when the failure occurs: at query time (null field), at render time (CDN 404), or rarely (static page). Use the lightest-weight approach that covers your actual failure mode.

routeLoader$

Server-side resolution. Best for nullable CMS fields. Zero JS overhead on client.

useSignal + onError$

Client reactive state. Best for CDN images that can fail post-render. Minimal lazy JS.

Inline onError$

Native browser event. No state tracking. Best for static docs pages where failures are rare.

Further reading

Docs and guides

Qwik City's routeLoader$ and useDocumentHead are documented at qwik.dev. For the general fallback pattern in any context, see the fallback.pics guide.

Implementation text
https://fallback.pics/docs/
https://fallback.pics/placeholder-image-api/
https://fallback.pics/blog/fix-broken-images-html-onerror/
https://fallback.pics/blog/astro-image-fallback-patterns/

Key takeaways

What to standardize before shipping

  • Resolve nullable CMS image fields in routeLoader$ so fallback URLs are baked into the serialized server HTML.
  • Use useSignal and onError$ for CDN images that can return 404 after initial page load.
  • Inline onError$ is simpler and lower-overhead for static content where image failures are rare.
  • Guard against infinite error loops: check the current src value before setting the fallback in any handler.
  • Set og:image using useDocumentHead with loader data so social crawlers see the correct resolved URL.

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