Why Thumbnail Editors Break After the First Growth Wave
Most thumbnail tools begin with one canvas and a couple of text overlays. Then teams add templates, effects, and export variants, and suddenly every change introduces visual regressions.
Today I want to show you a layered architecture that keeps a thumbnail editor maintainable as it scales.
Step 1: Treat each layer as immutable render input
type Layer = {
id: string;
kind: 'text' | 'image' | 'shape';
x: number;
y: number;
z: number;
visible: boolean;
props: Record<string, unknown>;
};
Step 2: Build deterministic render ordering
function orderedLayers(layers: Layer[]): Layer[] {
return [...layers]
.filter((l) => l.visible)
.sort((a, b) => a.z - b.z || a.id.localeCompare(b.id));
}
Step 3: Keep template operations as pure transforms
function applyBrandTemplate(layers: Layer[]): Layer[] {
return layers.map((l) =>
l.kind === 'text'
? { ...l, props: { ...l.props, fontFamily: 'Brand Sans', strokeWidth: 2 } }
: l,
);
}
Pitfalls
- Mutating layer arrays in place from multiple UI handlers.
- No stable render order when two layers share the same z-index.
- Template logic scattered across view components.
Verification
- Same project JSON produces identical output image twice.
- Template apply/revert actions are reversible.
- Layer reorder operations are covered by snapshot tests.