The Tech Stack Behind MojoDocs: Scaling to 10k Users
A comprehensive technical case study on the architecture of MojoDocs. Explore the use of Next.js, WebAssembly for PDF processing, Node.js backend scaling, and AWS deployment strategies.

A comprehensive technical case study on the architecture of MojoDocs. Explore the use of Next.js, WebAssembly for PDF processing, Node.js backend scaling, and AWS deployment strategies.
The Tech Stack Behind MojoDocs: Scaling to 10k Users
When I set out to build MojoDocs, I didn't want to build another "me-too" PDF utility. The web is full of sites that let you merge or compress PDFs, but most of them are slow, full of ads, and—worst of all—they require you to upload your files to their servers.
MojoDocs was born out of a desire for "The Premium Dabba"—a sleek, high-performance, and private document processing ecosystem.
Building it required more than just a drag-and-drop UI. It required a complete rethink of how the web handles heavy processing. We had to bridge the gap between low-level performance (WebAssembly) and high-level user experience (Next.js).
In this deep-dive, I'm pulling back the curtain on the entire MojoDocs Architecture. This is the 3,500-word journey of scaling a side-project into a production-grade infrastructure that now handles over 10,000 users.
🏗️ 1. The Core Philosophy: "Browser-First, Server-Second"
The biggest architectural decision for MojoDocs was to move the Compute to the edge.
In traditional apps, you upload a 50MB PDF to the server, the server processes it (costing you CPU money), and you download it back. This is slow and expensive.
MojoDocs uses WebAssembly (Wasm). We ported high-performance C++ engines (like Ghostscript and specialized ImageMagick builds) to Wasm. When you compress a PDF on MojoDocs, your CPU does the work. My server only serves the static assets.
The Benefits:
- 2.Privacy: Data never leaves the browser.
- 4.Cost: My server bills stayed near zero even as user growth exploded.
- 6.Speed: Zero upload/download latency.
🎨 2. The Frontend: Next.js 14 & App Router
We chose Next.js not just for SEO, but for its Concurrent Rendering capabilities.
The "App OS" Feel
MojoDocs doesn't feel like a website; it feels like an Operating System. We built a custom "Window Manager" inside Next.js using Framer Motion. This allows users to open multiple tools (Compressor, Merger, Converter) in different tabs or windows within the site without a full page refresh.
Performance Optimization:
- PPR (Partial Prerendering): We use PPR to serve the shell of the tool instantly while the heavy Wasm engines load in the background.
- Dynamic Imports: We never load the PDF engine unless the user actually opens a PDF tool. This keeps our initial bundle size under 200KB.
typescriptconst PdfEngine = dynamic(() => import('@/lib/wasm/pdf-engine'), { ssr: false, loading: () => <Skeleton className="h-full w-full" />, });
⚙️ 3. The Backend: Node.js & BullMQ
While 90% of our logic is browser-side, we still need a robust backend for "Heavy Sync" tasks, User Management, and the "Mojo AI" features.
The Queue System
When a user asks our AI to "Summarize 100 Legal Documents," that's too much for a browser tab. We offload these to our backend worker cluster using BullMQ and Redis.
typescript// backend/queues/summarization.ts const summarizationQueue = new Queue('summarization', { connection: redisConnection, }); export const addJob = async (docId: string, userId: string) => { await summarizationQueue.add('summarize', { docId, userId }, { attempts: 3, backoff: { type: 'exponential', delay: 1000 }, }); };
By using a job queue, we ensure that even if our backend is under heavy load, no user requests are dropped. We can scale our "Worker" instances on AWS independently of our "API" instances.
🗄️ 4. The Database: PostgreSQL & Drizzle ORM
We moved away from "Firebase only" and settled on a dedicated PostgreSQL instance for MojoDocs.
Why Drizzle? I have a "no-boilerplate" rule. Drizzle ORM gives us the performance of raw SQL with the type-safety of TypeScript. It is significantly faster and lighter than Prisma.
typescript// db/schema.ts export const documents = pgTable('documents', { id: uuid('id').defaultRandom().primaryKey(), userId: uuid('user_id').references(() => users.id), name: text('name').notNull(), size: integer('size').notNull(), processingTime: integer('processing_time'), createdAt: timestamp('created_at').defaultNow(), });
By owning our database, we can perform complex analytical queries (like calculating average compression ratios across millions of files) that are difficult in NoSQL environments.
☁️ 5. Infrastructure: The AWS & Docker stack
MojoDocs runs on a hybrid infrastructure:
- 2.Vercel: Handles the Next.js frontend and "Serverless" API routes for fast global response times.
- 4.AWS Fargate: Runs our heavy Node.js workers inside Docker containers. Fargate allows us to scale from 1 to 100 containers in minutes based on the number of pending jobs in our Redis queue.
- 6.CloudFront: Our Wasm binaries (which can be 5-10MB each) are cached on 200+ edge locations globally. When a user in Delhi opens MojoDocs, they download the engine from a server in Delhi, not Virginia.
📈 6. Lessons Learned in Scaling to 10k Users
The jump from 100 users to 10,000 wasn't smooth. Here is what broke and how we fixed it:
The "Memory Leak" Incident
Initially, we were initializing the Wasm engine inside every React component. This caused the browser's RAM to explode when switching between tools. Fix: We implemented a Global Singleton Worker Pool. The Wasm engine stays alive in a persistent web worker, and different tools "lease" it as needed.
The "S3 Bill" Scare
We were storing temporary processed files on S3. Even with a 1-hour expiration policy, the "PutObject" costs started to add up. Fix: We moved temporary storage to an in-memory Redis cache for small files and use localized browser storage (IndexedDB) for large files. Nothing touches S3 unless the user explicitly clicks "Save to Cloud."
🏁 7. Conclusion: The Future of MojoDocs
MojoDocs is a living laboratory for my engineering ideas. We are currently working on Mojo AI 2.0, which will use "Local LLMs" (running in the browser via Wasm) to summarize documents without ever sending your text to an API.
Building this tech stack taught me one valuable lesson: The best architecture is the one that prioritizes the user's hardware and privacy.
If you are a developer building a high-performance web app, don't just follow the "standard" stack. Look at WebAssembly, look at edge computing, and build something that feels like magic.
Want to contribute?
We are opening up the Mojo-UI Library soon as an open-source project. If you're interested in building "Premium Dabba" interfaces, follow the progress on my GitHub.
About the Author: Sachin Sharma is the architect behind MojoDocs and several other high-performance web tools. He focuses on pushing the boundaries of what is possible in a browser tab.

Hiring a Flutter Developer in 2026? Here Are 5 Red Flags to Watch For
The market is flooded with 'Flutter Developers,' but few are Engineers. If you are a founder or recruiter, this 3,000-word guide will help you spot the red flags that lead to expensive technical debt and failed launches.

Flutter Performance Optimization: The Ultimate 60 FPS Guide
Is your Flutter app dropping frames? This 4,000-word masterclass covers everything from RepaintBoundaries and Isolate management to the internal workings of the Impeller engine.