When we think about “UI,” we usually think of what’s built using front-end frameworks like React, Angular, or Vue — rendered in real-time, visually tweakable, and directly interactive in the browser.
But what if you need a UI that is shown in the browser, but isn’t built using front-end tools at all? What if the entire UI is generated on the backend, rendered as a static image — and not just one, but hundreds of such images, in bulk?
That was exactly the challenge we faced when building a bulk asset QR label generator. Each label had to be a high-resolution PNG containing:
- An encrypted QR code
- Structured asset information
- The tenant’s logo
- Clean, consistent layout
- Ready-to-print output
And most importantly, all of this had to be generated entirely on the backend, with no visual rendering tools to assist.
The Tools That Made It Possible
To achieve this, we leveraged the power of:
- sharp: For high-speed image generation and processing
- qrcode: To generate QR codes in SVG format
- SVG + manual layout calculations: For building visual structure without HTML/CSS • Node.js Worker Threads: To process batches of assets in parallel and improve performance • Cloud storage + email: To deliver final output via secure download links
Technical Hurdles
No Real-Time Feedback
In frameworks like React or Angular, you see the output as you code. Tweak a font size or alignment? Boom — instant visual feedback in the browser.
On the backend? Nothing like that.
I had to generate one image, expose it via an API, and manually hit it using tools like Postman or
browser extensions like ModHeader — just to view the result in the browser. Every small adjustment — spacing, alignment, font size — meant repeating that cycle over and over. It was like coding in the dark, one pixel at a time.
Layout and Sizing Without a Layout Engine
Text doesn’t wrap. Fonts don’t auto-resize. There’s no flexbox or grid to fall back on. I had to manually estimate character widths, truncate overflowing text, and hard-code alignment based on expected font metrics. Maintaining visual consistency across assets with varying data lengths was a serious challenge.
Logo Quality and Contrast
Tenant logos came in all shapes, sizes, and resolutions. Some looked washed out when printed on white. To fix that, we ran each logo through Sharp with contrast adjustments to make sure they remained crisp and legible in the final PNG.
Scaling with Worker Threads
Generating a single label was manageable, but doing it for hundreds of assets? That quickly became a bottleneck. We used Node.js worker threads to break processing into batches, parallelise image rendering, and avoid blocking the main event loop.
Resource Management
Each image had to be buffered, processed, and written to disk before zipping. Managing memory usage and file system writes efficiently was key, especially when processing large batches. One small memory leak or unhandled exception could crash the entire operation.
Final Output
Each run generated:
- Dozens to hundreds of PNG images, ready for printing
- A single zipped archive
- Uploaded to cloud storage
- A download link sent via email, allowing users to simply click and retrieve their batch of labels
Building UI on the frontend is about interactivity. Building UI on the backend is about precision, control, and resource efficiency — especially when it comes to image generation.
With the power of Node.js, Sharp, and worker threads, we transformed a layout problem into a fast, scalable backend service. It wasn’t flashy, but it was functional, flexible, and delivered real-world value.
Sometimes, the best UI isn’t rendered in the browser — it’s downloaded as a file.
Author:
The views and opinions expressed in this blog are those of the author and do not necessarily reflect the official policy, position, or views of nhance.ai or its affiliates. All content provided is for informational purposes only.