A ticket names a symptom and then guesses a cure. I have written before about a reported cause that was not the real cause, and this one went further. Those are two different things, and the guess is often wrong. Last week I got a one-line ticket that guessed wrong in a way that would have cost me a security hole, and the real fix turned out to be one line of code.
Here is how I checked the guess, and why I then decided not to build the better system I had already designed.
The ticket
The ticket read: “instead of PNG, components and template images can be saved as SVG for better quality.”
The context: one of the SaaS products I run engineering for has a design builder. Every time a user saves a component or a template, the product stores a small preview thumbnail for the card in their dashboard. Those thumbnails looked soft. Somebody looked at the softness and reasoned it out: PNG is raster, SVG is vector, vector stays sharp when you scale it, so we should save SVG.
That reasoning is correct in general. It is just not correct here. But you only find that out by looking.
What the ticket would have actually cost
The first thing I did was trace how a thumbnail gets made today. The browser captures it, using a library called html-to-image. That library can output SVG as well as PNG, so on paper the ticket really was a one-line change. Swap toBlob for the SVG call and go home.
Then I looked at what that library’s SVG actually is, and it is not a vector drawing of the design. It takes the whole HTML of the design, wraps it in a foreignObject tag inside the SVG, and embeds every font and image as base64 text. It is a browser screenshot wearing a vector costume.
That has four consequences, and I did not like any of them.
It is usually bigger than the PNG, not smaller, because the fonts ride along inside the file. It only renders correctly if every single font and image got embedded properly, and when one does not, you do not get a slightly wrong thumbnail, you get a blank one. It does not render in most non-browser tools, so the day somebody wants that thumbnail inside an email or a PDF, it breaks.
And then the one that ended the discussion. An SVG file can carry scripts. If the product accepts SVG uploads from the browser and then displays them back inside the app, that is a stored cross-site-scripting hole. Someone could put executable code in a thumbnail and have it run in another user’s session.
So the trade on the table was: accept a real security surface and cross-tool fragility, in order to fix thumbnails that look a bit soft. That is a bad trade at any price. I stopped there and went looking for the actual cause.
The real cause was density, not format
The capture code called the library with no pixelRatio option set. That means it captured at 1x, plain CSS-pixel resolution.
But the product then stretches that thumbnail to fill the whole width of the card. And most people are reading it on a 2x or 3x screen. A 1x image, stretched, on a retina display, looks soft. That is the entire complaint. There was never anything wrong with PNG.
The fix was to capture at pixelRatio: 2. The browser now grabs a sharper image and downscales it into the card, which is exactly how you get a crisp result. One option, five lines of change including the comment. Six different save flows in the product got fixed at once, because all six already went through the same capture helper.
No new file format. No security hole. No server change. No database change. I committed it the same day the ticket landed.
The lesson is not “SVG is bad.” SVG is fine. The lesson is that the ticket aimed at format when the cause was resolution, and nothing in the ticket would have told me that. Only reading the capture code told me that.
The better system I designed and then did not build
Tracing that code showed me something else, and this is the part where a lot of engineers get themselves in trouble, including me on a worse day.
The thumbnail today is a photograph the user’s browser takes of the live editor. That is fragile by its nature. It depends on their browser, on their fonts loading in time, on what is on screen at that moment. There was even dedicated code to strip the editor’s own overlay controls out of the photo, which is the design fighting itself.
There is a better architecture, and it is what the big design tools do. Do not photograph anything. The design is already saved as structured data. So let the server rebuild the design from that data and render the image itself, using the same rendering engine that already produces the live widget.
The wins are real. Edit a component and its thumbnail refreshes on its own. You can regenerate every thumbnail ever made with one command after any rendering fix, which you can never do with a photograph. Nothing gets uploaded from the browser at all, so the entire upload security question disappears. And the same engine can later produce preview images for emails, social share cards, and PDFs.
The good part is that we already own the rendering engine. It is the same code that renders the live widget. So this was never “build a renderer from scratch.” It was “put the renderer we have behind a queue, and add one new piece, a headless browser to turn that HTML into an image.”
I went and proved the new piece was viable. I put headless Chrome on the staging box, got it running, and measured what one render actually costs on the real hardware. About 250 MB of memory for roughly one second, then back to baseline. I wrote up the measurements and the deployment traps separately, because they are useful on their own.
Then I did not build it.
Why not building it was the decision
The one-line fix had already removed the pain. Nobody was looking at soft thumbnails anymore. And the moment a problem stops hurting, the honest question changes from “is this architecture better” to “is this the most valuable thing I could build this month for this business.”
It was not. The photograph approach is fragile, but it has been fragile for years and it has not been the thing costing the business money. Building the queue, wiring the worker, deploying and monitoring Chrome on a production box with no swap, and backfilling every existing thumbnail is real work with real operational surface. That is the part teams routinely underprice, and it is most of what modernizing actually costs. I would be adding a browser to the production stack, permanently, to solve a problem that no longer had a symptom.
So it went into the design notes with the measurements attached. When there is a second reason to want server-side rendering, and there will be, most likely the day someone asks for preview images in emails, the feasibility work is already done and the decision takes an hour instead of a week.
That is what I mean by the cheap fix protecting the deep fix. Shipping the one-liner did not cancel the architecture. It removed the urgency, which is the thing that makes people build big systems badly.
What I would tell a founder from this
If you run a product and you are handing tickets to a developer, the useful thing to know is that the format of a ticket hides this problem. A ticket says “do X.” It rarely says “we are seeing Y.” By the time it reaches the person who will build it, the diagnosis has already been made by someone who was not looking at the code, and it now looks like a decision rather than a guess.
Three things I actually do about that.
I treat the named solution in a ticket as a hypothesis, never as the scope. Deciding what is worth building is the judgment that does not automate, which is why verifying is now the real bottleneck. The scope is the symptom. Here the symptom was “thumbnails look soft” and the hypothesis was “so use SVG,” and separating those two took twenty minutes and saved a security review.
I look for the cheap fix that kills today’s pain before I design anything, because the deep fix built under pressure is almost always the wrong shape.
And when I find a better architecture, I decide whether it earns its operational cost right now, in this business, this month. Not whether it is better in principle. It usually is better in principle. That is not the question.
The thumbnails are sharp. The codebase did not grow. That is a good week.