If you've ever run Chromatic or Percy on a Storybook that pulls images from Lorem Picsum, you know the pain. Every snapshot run fetches a different image. Your baseline diffs never settle. You end up suppressing noise instead of catching real regressions. It's a slow leak that eventually makes your visual testing pipeline worthless.
The fix is boring: use deterministic image URLs. Every run, same image. No surprises.
The problem with random placeholder URLs
Here's a typical story using Lorem Picsum:
// ProductCard.stories.jsx
export const Default = {
args: {
title: "Wireless Headphones",
price: "$79",
image: "https://picsum.photos/400/300", // different image every time
},
};
picsum.photos/400/300 serves a random photo on every request. That's fine for visual design work, but it means every snapshot run is comparing against a different image. Your visual diff tool flags the image as changed — because it is changed — and your team either ignores the alert or spends 10 minutes confirming it's noise.
Some teams work around this by seeding Picsum with a fixed ID (picsum.photos/id/42/400/300), but now you're hardcoding a photo of a random European city in your card component stories. That's weird, and it'll break if that photo ever gets removed.
The after: PlacePack API URLs
PlacePack generates placeholder images with labeled dimensions and customizable colors. More importantly: the same URL always returns the same image. It's a pure function over its inputs.
// ProductCard.stories.jsx
export const Default = {
args: {
title: "Wireless Headphones",
price: "$79",
image: "https://placepack.top/api/v1/product-card:400x300.svg",
},
};
The label (product-card) renders inside the placeholder. The dimensions are baked in. Run this story 1000 times — same SVG, same pixels, no diff noise.
For PNG snapshots (Chromatic, Percy, Playwright Component Testing), use .png instead:
image: "https://placepack.top/api/v1/product-card:400x300.png"
Real-world example: a component with multiple image slots
Here's a HeroSection story that uses PlacePack for both the hero background and the author avatar:
// HeroSection.stories.jsx
import { HeroSection } from "../components/HeroSection";
const BASE = "https://placepack.top/api/v1";
export default {
title: "Marketing/HeroSection",
component: HeroSection,
};
export const Default = {
args: {
backgroundImage: `${BASE}/hero-bg:1200x630.png`,
authorAvatar: `${BASE}/avatar:80x80.png`,
authorName: "Jane Smith",
headline: "Ship faster with better placeholders",
},
};
export const DarkTheme = {
args: {
...Default.args,
backgroundImage: `${BASE}/hero-bg:1200x630.png?bg=1a1a2e&color=ffffff`,
authorAvatar: `${BASE}/avatar:80x80.png?bg=1a1a2e&color=ffffff`,
},
};
The URL parameters bg and color let you customize the fill and text color to match your brand or theme — still deterministic, still stable.
Using the presets endpoint
If you're setting up Storybook for a new project and aren't sure what sizes to use, hit the presets endpoint:
curl https://placepack.top/api/v1/presets
It returns named presets for common use cases — hero banners, thumbnails, avatars, open graph images — with the exact dimensions already filled in. You can use those names directly in your API URLs:
// generate-story-fixtures.mjs
const res = await fetch("https://placepack.top/api/v1/presets");
const { presets } = await res.json();
// presets.storybook contains the sizes relevant to Storybook stories
console.log(presets.storybook);
// [{ label: "hero", width: 1200, height: 630 }, { label: "thumb", width: 400, height: 300 }, ...]
You can run this script once to generate a fixtures.js file that exports all your placeholder URLs keyed by name:
// scripts/generate-placeholders.mjs
import { writeFileSync } from "fs";
const res = await fetch("https://placepack.top/api/v1/presets");
const { presets } = await res.json();
const storybook = presets.storybook ?? [];
const urls = Object.fromEntries(
storybook.map(({ label, width, height }) => [
label,
`https://placepack.top/api/v1/${label}:${width}x${height}.png`,
])
);
writeFileSync(
"src/test-utils/placeholders.js",
`export const placeholders = ${JSON.stringify(urls, null, 2)};\n`
);
Then import it everywhere:
import { placeholders } from "@/test-utils/placeholders";
export const Default = {
args: { image: placeholders.thumb },
};
A note on network access in CI
If your Storybook CI environment blocks outbound requests, you have two options:
- Use the pack endpoint to download PNG/SVG files locally and commit them as fixtures (covered in the visual regression article).
- Allow outbound HTTPS to
placepack.top— the API is free, no key needed, and rate-limited to 30 requests per 60 seconds, which is plenty for a story build.
The API requires no authentication. No signup, no token rotation, no surprise 402s because you hit a free tier ceiling.
Try it
The quickest way to see what your stories will look like: open the generator at placepack.top, type a label and size, pick your colors, and copy the API URL directly.
More Storybook-specific guidance — including a Storybook decorator that auto-replaces broken image src values — lives at placepack.top/storybook.
Top comments (0)