Keeping Before-and-After Image Examples Verifiable
Why this matters
Before-and-after images look like content, but they behave like compiled artifacts. A page can keep rendering while its caption reports an old byte count, an "after" file was generated with different settings, or one asset quietly changed dimensions.
I encountered that boundary in an image-tool project with six example pages. Each page showed two cases, and every case had a before and an after asset. That is 24 files whose bytes, dimensions, and descriptive metadata need to agree.
The useful fix was not another screenshot review. It was to make the examples executable: generate them from controlled inputs, describe their expected properties in code, and fail a verifier when disk and metadata diverge.
What I built or tested
The page data uses a typed record for each case. The important fields are not presentation-only:
type ExampleAsset = {
alt: string;
bytes: number;
format: string;
src: string;
};
type ExampleCase = {
id: string;
before: ExampleAsset;
after: ExampleAsset;
outputNote: string;
};
The byte count is deliberately exact. It connects the label shown to a reader with the file committed to the site. The source path connects the same record to the asset the browser requests. The format and output note make the transformation understandable, while decoded dimensions are enforced by the verifier.
This contract is small enough to review. It is also strict enough to answer a practical question: does the page still describe the files that ship?
Setup
The project keeps source images outside the public example directory. An input-generation script prepares ten named inputs, copying PNG sources when no conversion is needed and using explicit JPEG or WebP settings for the others.
A second script finds every -before asset for six tool slugs and creates the matching -after file. The transformations are explicit:
- JPEG uses quality 78, MozJPEG, and progressive output.
- WebP uses quality 78 and smart subsampling.
- PNG uses quality 78, compression level 9, and progressive output.
- Every input is autorotated before encoding.
The generator then reports the observed input and output byte counts. Those observations are what should be reviewed and transferred into the typed records. Do not compute a reduction percentage once and leave it disconnected from the files.
The generator produces artifacts; the contract and verifier make them reviewable.
Step-by-step walkthrough
The verifier iterates through every before and after record. For each asset, it performs three checks:
- The referenced file is accessible.
- Its actual byte count equals the declared count.
- Sharp can decode it and reports the expected 1536 by 1024 dimensions.
A compact implementation looks like this:
const file = await stat(filename);
const metadata = await sharp(filename).metadata();
if (file.size !== asset.bytes) {
failures.push(`${asset.src}: byte count changed`);
}
if (metadata.width !== 1536 || metadata.height !== 1024) {
failures.push(`${asset.src}: dimensions changed`);
}
Collecting failures is better than throwing on the first mismatch. A regeneration can affect several records, and one verifier run should give the author the complete repair list.
The command exits nonzero when any failure exists. That detail turns a useful local report into a CI gate.
What went wrong
A clean run proved that all 24 assets across the six pages matched their records. That only tested the success path, so I built an isolated failure experiment.
The experiment copied the public example directory to a temporary location and ran the real repository verifier against it. It then appended one byte to a copied JPEG and ran the verifier again.
Why append a byte? The image remained decodable, and its width and height stayed correct. A verifier that only opened the image or checked dimensions would accept it. The byte contract should reject it.
The second run exited with status 1 and reported that the file contained 145155 bytes instead of the expected 145154. The temporary fixture was deleted afterward, and the source repository was never modified.
How I verified it
This is a useful pattern for repository checks in general: create a valid baseline, introduce one controlled violation, and assert both the failure status and the diagnostic. A green check is more credible after its red path has been observed.
The experiment asserted the clean verifier's asset and page counts, then asserted both the nonzero failure status and the precise byte-mismatch diagnostic. This distinguished "the script ran" from "the script enforced the intended contract."
Fix or mitigation
The repeatable sequence is:
1. Change source images or transformation settings.
2. Regenerate before and after assets.
3. Review the generated byte summary and visual output.
4. Update the typed records in the same change.
5. Run the asset verifier.
6. Let CI reject any unreviewed drift.
Keep the generator and verifier separate. Generation is an intentional write; verification is a read-only check. If a CI check silently regenerated files, it could hide the fact that committed artifacts were stale.
Also pin the runtime and image library versions used for generation. Exact encoded bytes can change across encoder versions even when quality settings are unchanged. That is not a reason to weaken the check; it is a reason to make toolchain upgrades explicit and review their output.
Trade-offs
Exact bytes are not a perceptual-quality metric. Two outputs can be visually equivalent with different bytes, and identical dimensions say nothing about whether important texture survived compression. Visual review or perceptual comparisons are a separate gate.
The record also does not prove that a marketing claim is fair. It proves that the declared size and dimensions match the shipped sample under the documented settings.
Finally, strict byte checks are a poor fit when files are intentionally recompressed after the build by an uncontrolled pipeline. In that system, verify the pre-upload artifact and separately test delivery invariants such as dimensions, format, or a content digest exposed by the asset service.
Conclusion
Treat demo assets the way you treat generated code:
- preserve their inputs,
- make transformations explicit,
- store reviewable expected metadata,
- verify the committed outputs without rewriting them, and
- exercise a real failure case.
That changes before-and-after examples from manually trusted decoration into a small, enforceable build contract. The page can still tell a visual story, but CI now checks that the story matches the files.

Top comments (0)