DEV Community

Cover image for How I Show Car Photos Without Storing a Single Image
Roger Rosset
Roger Rosset

Posted on

How I Show Car Photos Without Storing a Single Image

When I started building AutoFeedback — a European car review platform — I ran into an obvious problem early: I needed photos of thousands of car models, but I had no budget for a car photo API, no CDN costs to absorb, and no time to manually source and host images.

The solution turned out to be elegant, free, and hiding in plain sight.

Car Review Model Banner

The Problem

Most car data APIs are either expensive, require licensing agreements, or only cover popular markets. I have models like the Renault Clio, SEAT Ibiza, and Fiat Punto — bread-and-butter European cars that might not even appear in a US-centric paid API.

And even if I found a source, hosting thousands of images on R2 or S3 would mean:

  • A pipeline to download and store images
  • Storage costs that grow with the catalogue
  • Maintenance when images go stale

There had to be a better way.

The Solution: Wikimedia Commons

Wikimedia Commons is a free media repository with millions of images — including an enormous collection of car photos, all under Creative Commons licenses. And it has a public JSON API that requires no authentication, no API key, and has no per-request cost.

The trick: search Commons for "BMW 3 Series" and grab the first image result. Done.

const url =
  "https://commons.wikimedia.org/w/api.php" +
  "?action=query" +
  "&generator=search" +
  "&gsrsearch=" +
  encodeURIComponent(`${brand} ${model}`) +
  "&gsrnamespace=6" + // namespace 6 = File/Image namespace only
  "&prop=imageinfo" +
  "&iiprop=url" +
  "&format=json" +
  "&origin=*"; // enables CORS for browser requests

const response = await fetch(url);
const data = await response.json();
const pages = data.query?.pages;
const firstPage = Object.values(pages)[0];
const imageUrl = firstPage?.imageinfo?.[0]?.url;
Enter fullscreen mode Exit fullscreen mode

That's it. I get a direct URL to a full-resolution image hosted on Wikimedia's CDN. My server never touches it.

The Svelte Component

I wrapped this in a VehicleImage component that handles loading, errors, and the fallback gracefully:

<script lang="ts">
  import { onMount } from 'svelte';

  export let brand: string;
  export let model: string;
  export let year: number | null = null;

  let imageUrl: string | null = null;
  let loading = true;
  let error = false;

  $: searchQuery = year ? `${brand} ${model} ${year}` : `${brand} ${model}`;

  async function loadImage() {
    loading = true;
    error = false;
    try {
      const url = 'https://commons.wikimedia.org/w/api.php' +
        '?action=query&generator=search' +
        '&gsrsearch=' + encodeURIComponent(searchQuery) +
        '&gsrnamespace=6&prop=imageinfo&iiprop=url&format=json&origin=*';

      const res = await fetch(url);
      const data = await res.json();
      const pages = data.query?.pages;
      const first = Object.values(pages ?? {})[0] as any;
      imageUrl = first?.imageinfo?.[0]?.url ?? null;
      if (!imageUrl) error = true;
    } catch {
      error = true;
    } finally {
      loading = false;
    }
  }

  onMount(loadImage);
</script>

{#if loading}
  <!-- Spinner -->
{:else if error || !imageUrl}
  <!-- Fallback placeholder with car icon -->
{:else}
  <img src={imageUrl} alt="{brand} {model}" class="w-full h-full object-cover" />
{/if}
Enter fullscreen mode Exit fullscreen mode

The key insight: the fetch happens in the browser, not on the server. This means:

  • Zero server load
  • Zero storage costs
  • Images are served from Wikimedia's global CDN directly to the user
  • If Wikimedia is down, we show a graceful fallback — no error thrown

Why Client-Side?

I'm running on Cloudflare Workers, which has a 10ms CPU limit on free tier for some operations. A server-side fetch to Wikimedia for every page load would be wasteful and could hit timeouts. By doing it client-side with onMount, the page loads instantly with SSR content, and the image "pops in" after — a much better perceived performance story.

The Caveat: First Result Isn't Always Perfect

The first Wikimedia search result isn't guaranteed to be a perfect photo of the exact trim. Sometimes you get:

  • A photo from a slightly different year
  • A factory or motor show image
  • Occasionally an interior shot

That's why I added a disclaimer across all 6 languages:

"Images are illustrative and may not exactly represent the described model."

For a community review platform, this is totally acceptable. Users aren't buying from us — they're reading reviews. An approximate image is far better than a blank box.

The Legal Side: Attribution Matters

Here's something easy to miss: Wikimedia Commons images are not public domain by default.

Most are under Creative Commons licenses, which have real requirements:

License What it requires
CC BY Credit the author
CC BY-SA Credit the author + share derivatives under the same license
Public Domain No restrictions — use freely

If you just grab the URL and display the image with no attribution, you may be violating the license — even though Wikimedia doesn't technically block hotlinking.

Fetching Attribution Metadata

The good news: the same API call that returns the image URL can also return everything you need to comply. You just need to add extmetadata to the iiprop parameter:

const url =
  "https://commons.wikimedia.org/w/api.php" +
  "?action=query" +
  "&generator=search" +
  "&gsrsearch=" + encodeURIComponent(searchQuery) +
  "&gsrnamespace=6" +
  "&prop=imageinfo" +
  "&iiprop=url|extmetadata" +                          // <-- add this
  "&iiextmetadatafilter=Artist|LicenseShortName|LicenseUrl" + // only what we need
  "&format=json" +
  "&origin=*";
Enter fullscreen mode Exit fullscreen mode

The extmetadata object in the response contains:

  • Artist.value — the author, often as an HTML string like <a href="...">John Doe</a>
  • LicenseShortName.value — e.g. "CC BY-SA 4.0"
  • LicenseUrl.value — e.g. "https://creativecommons.org/licenses/by-sa/4.0"

Rendering the Attribution

Once you have the metadata, strip the HTML from the author field (since you don't want to blindly inject it) and render a proper <figcaption>:

<figure>
  <img src={imageUrl} alt="{brand} {model}" class="w-full object-cover" />
  <figcaption class="text-xs text-gray-400 mt-1">
    Via <a href={pageUrl} target="_blank" rel="noopener noreferrer">Wikimedia Commons</a>
    {#if author} · {author}{/if}
    {#if licenseShortName}
      · <a href={licenseUrl} target="_blank" rel="noopener noreferrer license">{licenseShortName}</a>
    {/if}
  </figcaption>
</figure>
Enter fullscreen mode Exit fullscreen mode

This renders something like:

Via Wikimedia Commons · Vauxford · CC BY-SA 4.0

Which is exactly what the license requires — and it adds credibility to the page rather than detracting from it.

What About Hotlinking?

Wikimedia explicitly allows external image embedding via their CDN (upload.wikimedia.org). They don't block it. But their Terms of Use and the individual file licenses still apply. Serving images without attribution is technically a license violation even if it works technically.

The correct mental model: hotlinking is permitted, attribution is required.

The Result

  • 0 images stored in my database or on any storage bucket
  • 0 API keys to manage or rotate
  • 0 monthly costs for image serving
  • Works for every European car brand that has any Wikipedia/Commons presence
  • Graceful fallback for truly obscure models
  • Full legal compliance with a single extra API field and four lines of HTML

The entire solution is ~50 lines of Svelte. Sometimes the best engineering is finding the data that already exists, pointing at it — and reading the license.

Top comments (0)