How to Build a Serverless, Zero-Database Web App for 100k+ Users Using Client-Side Image Processing
To let users use their own images without uploading them to a remote server, we utilize the HTML5 File API. When a user drags and drops a folder of images into the asset pool, we do not upload them. Instead, we generate a local reference link.
Using URL.createObjectURL(file) is highly performant because it creates a temporary, unique URL string representing the file in the browser’s local memory.
// Handling local file dropping without backend storage const handleImageDrop = (files) => { const newAssets = Array.from(files).map((file) => ({ id: generateUniqueId(), src: URL.createObjectURL(file), // Generates a local, temporary browser URL name: file.name, })); // Update state to render previews instantly setAssetPool((prev) => [...prev, ...newAssets]); };
This approach provides instantaneous rendering. The user sees their images in the tool within milliseconds because there is zero network latency.
The core of a Tier List Maker is a multi-row matrix. Each row (S, A, B, C, etc.) is a drop zone, and there is a master asset pool.
We model this state as a key-value object where each key represents a row ID and the value is an array of item objects. To make the dragging experience smooth and accessible on both desktop and mobile, we implemented a custom pointer-event handler.
interface TierItem { id: string; src?: string; // For images text?: string; // For our custom "Text Mode" } interface TierBoardState { [rowId: string]: TierItem[]; }
By supporting both text strings and image elements within the same state shape, we allowed users to build conceptual lists (ranking books, coding frameworks, or life goals) seamlessly using our integrated Text Mode without requiring heavy image assets.
The most technically challenging part of a backend-less approach is exporting the final grid as a high-quality, shareable image. Many applications rely on server-side APIs running headless Chromium to capture screenshots. This is incredibly expensive to scale.
To solve this, Rankly performs image synthesis directly in the browser using the Canvas API.
When the user clicks “Export,” we calculate the absolute bounding box of the tier list element, initiate an off-screen HTML5 element, and draw the grid step-by-step:
Calculate Dimensions: Determine the total width and height based on the number of active rows and the width of the container.
Draw Backgrounds and Borders: Paint the structural layout of the board.
Render Labels: Draw the text labels (S, A, B, C…) using local system fonts.
Draw Images/Text Items: Loop through the state of each row. For images, we instantiate an Image() object, assign the local Object URL as the source, and draw it onto the corresponding coordinates using ctx.drawImage().
Convert to Downloadable Blob: Export the canvas to a data URL and trigger an automatic download.
// High-level conceptual flow for client-side export const exportToPNG = async (boardState) => { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); // Set dimensions based on board layout canvas.width = calculatedWidth; canvas.height = calculatedHeight; // Sequential canvas drawing logic... await drawGridStructure(ctx, boardState); // Trigger local browser download const imageURL = canvas.toDataURL('image/png'); const downloadLink = document.createElement('a'); downloadLink.href = imageURL; downloadLink.download = 'my-tier-list.png'; downloadLink.click(); };
By moving all computation to the client, we have created a highly scalable architecture:
Infinite Scale, Zero Cost: Since our hosting consists entirely of static HTML, CSS, and JS files, we can serve millions of users using free CDN platforms like Cloudflare or Vercel. Our hosting costs remain virtually zero.
Superior GDPR Compliance: Because we do not transmit, process, or store personal user assets, we are naturally compliant with global privacy laws.
Instantaneous Feedback: Zero network round-trips mean that actions like dragging, adding rows, changing colors, and exporting happen instantly.
If you are interested in exploring how a high-performance, client-side editor behaves under real conditions, check out the live implementation of our Tier List Maker.
Let me know in the comments how you manage local state and client-side rendering in your own serverless projects!
Fuente: Artículo original