Skip to content
gilltrish.dev

Project

Filenames as a lookup table: a tiny trick for product builders

A product builder for made-to-order shelving displays, where a real photo updates with every choice. Over 1,000 photos are named after the options they show, so finding the right one is a simple lookup, with a fallback for combinations that don't have a photo.

My role
Full-stack developer
Stack
  • react
  • vite
  • tailwind
  • laravel

Why this matters

Product builders, "customize your order" pages and preview screens are everywhere, and they all have this problem.

Without a system, you end up with a huge `if/else` tree mapping choices to images. It's fragile and miserable to update.

A naming convention lets the photographer and the developer agree on one rule, and the code stays tiny no matter how many photos there are.

Explain it like I’m five

  • Naming convention: An agreed recipe for names, like labelling jars "flavour-size-date". If everyone follows it, you can find any jar without looking inside.
  • Template string / template literal: A string with blanks you fill in using ${}. Mad Libs for code.
  • Lookup table / map / dictionary: An object you ask "what's stored under this name?" and it answers instantly, like a coat-check ticket.
  • import.meta.glob (Vite): "Hey bundler, grab every file in this folder that matches this pattern and give me a list of them." Saves you writing 1,000 import lines.
  • Bundler / build step: The tool that packs your code and images into files a browser can load.
  • Fallback: Plan B (and C, and D) for when Plan A doesn't work.
  • Placeholder image: The "photo coming soon" picture you show when there's nothing better.
  • .webp: An image format like JPG, just smaller.

Step 1:Agree on the naming recipe before anyone takes photos.

Decide the order of the options and a short code for each value. Write it down somewhere the photographer can see it.

Text
{wood}_{height}_{shelves}shelves.webp
oak_tall_5shelves.webp
walnut_short_3shelves.webp

Tip:

Use short codes (oak, not Oak Veneer Premium) and keep the order fixed. The order is the recipe.

Step 2:Turn the customer's choices into a filename.

TypeScript
const WOOD_CODES = { 'Oak': 'oak', 'Walnut': 'walnut' }

function buildFilename(choices) {
  const wood = WOOD_CODES[choices.wood]
  const { height, shelves } = choices

  // If anything is missing, there is no filename to build yet.
  if (!wood || !height || !shelves) return null

  return `${wood}_${height}_${shelves}shelves.webp`
}

Step 3:Load every photo into a lookup table, once.

With Vite, import.meta.glob grabs a whole folder. We key the result by just the filename so lookups are simple.

TypeScript
const files = import.meta.glob('./photos/*.webp', { eager: true, import: 'default' })

// { './photos/oak_tall_5shelves.webp': '/assets/oak_tall_5shelves-a1b2.webp', ... }
// becomes
// { 'oak_tall_5shelves.webp': '/assets/oak_tall_5shelves-a1b2.webp', ... }
const photos = {}
for (const [path, url] of Object.entries(files)) {
  photos[path.split('/').pop()] = url
}

Step 4:Look it up and show it.

TypeScript
const filename = buildFilename(choices)
const src = (filename && photos[filename]) || placeholder
TypeScript
<img src={src} alt="Preview of your bookshelf" />

Step 5:Add a fallback ladder for the "in-between" moments.

Photos come in levels: the base unit, then base + doors, then base + doors + lights. Try the most specific level first and step down until something matches. That way the customer keeps seeing the last good photo instead of a placeholder the moment they tick "add doors" but haven't picked a door style yet.

TypeScript
function pickPhoto(choices) {
  const levels = []
  levels.push('base')
  if (choices.doors) levels.push('doors')
  if (choices.lights) levels.push('lights')

  // Most specific first
  for (const level of levels.reverse()) {
    const name = buildFilenameFor(level, choices)
    if (name && photos[name]) return photos[name]
  }
  return placeholder
}
  1. Most specific photoWood + doors + lights
  2. Less specificWood + doors
  3. Base photoJust the wood
  4. PlaceholderPhoto coming soon
Try each one in order and stop at the first photo that exists.

Step 6:Fill early gaps with sensible defaults, and be honest about it.

On step 1 the customer has only picked a wood, but you still want a photo. Borrow a "typical" value for anything not chosen yet, and show a small note so nobody thinks it's their exact build.

TypeScript
const DEFAULTS = { wood: 'Oak', height: 'tall', shelves: 5 }

const photoChoices = {
  wood: choices.wood ?? DEFAULTS.wood,
  height: choices.height ?? DEFAULTS.height,
  shelves: choices.shelves ?? DEFAULTS.shelves,
}
const isExample = !choices.height || !choices.shelves
TypeScript
{isExample && <p role="status">Example photo — may not match your exact choices yet.</p>}

Step 7:(Optional) A tiny script to check what's missing.

Keep a list of every filename you expect, and have a Node script compare it with what's in the folder. It turns "are all the photos here?" from guesswork into a number.

What could go wrong

  • Spreading defaults the wrong way round. { ...DEFAULTS, ...choices } looks right, but if choices.height is null, null wins and wipes out the default. Use ?? per field (like step 6), or strip out empty values first.
  • Case sensitivity. Oak_Tall.webp and oak_tall.webp can both "work" on your Windows or Mac laptop, then break on a Linux server. Pick lowercase (or uppercase) and stick to it.
  • Impossible combinations. Some combos can't physically exist, so a photo will never come. Either stop people choosing them (clear the invalid follow-up answer when an earlier one changes) or make sure the fallback ladder handles them gracefully.
  • Options that aren't in the photo. If one choice (a material, a finish) was never photographed, the image won't change when it's picked. Say so on screen rather than letting people think the app is broken.
  • Combinations multiply fast. 3 woods × 3 heights × 4 shelf counts × 3 extras is already 108 photos. Count the combinations before the photoshoot is booked.
  • Missing files fail silently. A typo in one filename just shows the fallback. Log a warning in development (if (import.meta.env.DEV) console.warn(...)) so you notice.
  • Bundle size. Hundreds of images bundled with the app increase build time and repo size. That's fine at this scale, but past a few thousand it's worth moving them to a CDN.

What I’d tell past me

  • Lock the naming recipe in writing with whoever makes the images before the first batch arrives. Renaming 1,000 files later is no fun.
  • Write the "what's missing?" script on day one, not when the third batch of photos lands.
  • Design the fallback ladder early. "What do we show while they're choosing?" turned out to be a bigger question than "what do we show at the end?"
  • An honest "this is an example photo" note beats a perfect-looking preview that's quietly wrong.

← All projects