How I Built a Privacy-First PDF Editor That Runs Entirely in Your Browser (And Serves 50,000 Users with Zero Server Costs)
Why Client-Side Processing is the Future of Privacy-Focused Web Apps
By Maaz, Founder of PDFfreeEditor
The Problem That Started Everything
Six months ago, I needed to compress a PDF of my tax return to email it. I Googled “compress PDF,” clicked the first result, and watched my sensitive financial document upload to a random server somewhere.
That moment made me uncomfortable. Where did my file go? Who has access? Is it really deleted after “one hour” like they claim?
I’m a developer. I knew there had to be a better way.
So I built PDFfreeEditor By Techrex – a completely free PDF tool that processes files entirely in your browser. No uploads. No servers. No tracking.
Today, it serves over 50,000 users monthly with literally zero server infrastructure costs.
Here’s how I built it, the technical decisions behind it, and what I learned about building privacy-first web applications.
The Architecture: Why Client-Side Processing?
Most web apps follow this pattern:
User uploads file → Server processes → User downloads result
This works, but it has problems:
-
Privacy concerns: Your data passes through someone else’s infrastructure
-
Latency: Upload time + processing time + download time
-
Scaling costs: More users = more server resources = higher bills
-
File size limits: Servers need to limit uploads to manage costs
What if we could skip the server entirely?
User selects file → Browser processes → User downloads result
This is what PDFfreeEditor does. Everything happens in your browser using WebAssembly.
The Tech Stack: Building Without a Backend
Core Technologies
1. PDF.js (Mozilla’s PDF Library)
javascript
import * as pdfjsLib from 'pdfjs-dist';
// Load PDF in browser
const loadingTask = pdfjsLib.getDocument(fileUrl);
const pdf = await loadingTask.promise;
PDF.js lets you parse, render, and manipulate PDFs entirely client-side. Mozilla built it for Firefox’s PDF viewer, but you can use it standalone.
2. WebAssembly for Compression
JavaScript is great, but for CPU-intensive tasks like PDF compression, you need something faster. Enter WebAssembly.
javascript
// Load WebAssembly module
const wasm = await WebAssembly.instantiateStreaming(
fetch('pdf-compressor.wasm')
);
// Compress PDF data
const compressed = wasm.instance.exports.compress(pdfData, quality);
WebAssembly runs at near-native speed. For compressing a 10MB PDF, the difference is dramatic:
-
Pure JavaScript: ~8-12 seconds
-
WebAssembly: ~2-3 seconds
3. No Framework (Intentionally)
I built PDFfreeEditor with vanilla JavaScript. No React, no Vue, no framework at all.
Why?
-
Smaller bundle: 200KB total vs 500KB+ with a framework
-
Faster load time: Critical for first-time users
-
No build complexity: Simple deployment to Cloudflare Pages
Sometimes the best framework is no framework.
Privacy by Architecture (Not Policy)
Most apps claim to be “privacy-focused” by writing a privacy policy that promises they won’t misuse your data.
That’s privacy by policy.
PDFfreeEditor uses privacy by architecture – I literally can’t access your files even if I wanted to.
How It Works
1. Files Never Leave the Browser
javascript
// Traditional approach (uploads to server)
const formData = new FormData();
formData.append('file', pdfFile);
fetch('/api/compress', { method: 'POST', body: formData });
// PDFfreeEditor approach (stays in browser)
const fileReader = new FileReader();
fileReader.onload = async (e) => {
const arrayBuffer = e.target.result;
const compressed = await compressPDF(arrayBuffer);
downloadFile(compressed);
};
fileReader.readAsArrayBuffer(pdfFile);
The file data never hits the network. Open DevTools → Network tab while using the tool. You’ll see zero uploads.
2. No Analytics on File Contents
I use Plausible Analytics for basic usage stats (page views, button clicks), but I deliberately don’t track:
-
File names
-
File sizes
-
Which features you use on which files
-
How many files you process
Why? Because I don’t need to know, and you don’t need to tell me.
3. Works Completely Offline
Once the page loads, you can turn off WiFi and PDFfreeEditor still works. Try it:
-
Visit pdffreeeditor
-
Turn on airplane mode
-
Compress a PDF
It works because everything needed is already downloaded to your browser.
Technical Challenges (And How I Solved Them)
Challenge 1: File Size Limits
Problem: Browsers have memory limits. Loading a 500MB PDF into memory can crash the browser.
Solution: Chunked processing
javascript
async function processLargePDF(file) {
const chunkSize = 10 * 1024 * 1024; // 10MB chunks
const chunks = Math.ceil(file.size / chunkSize);
for (let i = 0; i < chunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
await processChunk(chunk);
// Allow browser to breathe
await new Promise(resolve => setTimeout(resolve, 100));
}
}
This processes files in chunks, preventing memory overflow.
Challenge 2: Cross-Browser Compatibility
Problem: WebAssembly support varies across browsers.
Solution: Progressive enhancement
javascript
async function initPDFProcessor() {
if (typeof WebAssembly === 'object') {
// Use fast WASM version
return await loadWasmProcessor();
} else {
// Fallback to slower JS version
return loadJavaScriptProcessor();
}
}
Modern browsers get the fast WASM version. Older browsers get a slower but functional JS fallback.
Challenge 3: Download Speed
Problem: Generating a download link for a 50MB file is slow.
Solution: Blob URLs with streaming
javascript
// Bad: Creates entire file in memory first
const blob = new Blob([largeData], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
// Good: Stream data as it's generated
const stream = new ReadableStream({
start(controller) {
processChunks(data, chunk => controller.enqueue(chunk));
controller.close();
}
});
const response = new Response(stream);
const blob = await response.blob();
const url = URL.createObjectURL(blob);
This starts the download faster and uses less memory.
The Economics: $0 Server Costs
Here’s my complete infrastructure cost for serving 50,000 users:
Hosting: $0 (Cloudflare Pages free tier) CDN: $0 (included with Cloudflare) Database: $0 (no backend, no database) Processing: $0 (happens in users’ browsers)
Total monthly cost: $0
Compare this to a traditional PDF service:
-
Server instances: $50-200/month
-
S3 storage: $20-100/month
-
CDN bandwidth: $30-150/month
-
Database: $25-100/month
Total: $125-550/month minimum
By moving processing to the client, I eliminated the entire server infrastructure cost.
The Trade-offs
Client-side processing isn’t perfect. Here are the honest trade-offs:
Pros:
-
Zero server costs
-
Perfect privacy (no uploads)
-
Works offline
-
Scales infinitely (users provide the compute)
Cons:
-
Slower for huge files (500MB+)
-
Requires modern browser
-
Can’t do some features (OCR is limited)
-
Users with slow devices have slower experience
For my use case (privacy-focused PDF editing), the pros massively outweigh the cons.
What I’d Do Differently
1. Start with TypeScript
I wrote PDFfreeEditor in vanilla JavaScript. Six months in, I wish I’d used TypeScript.
PDF data structures are complex. Type safety would have caught bugs earlier and made refactoring easier.
2. Add Better Error Messages
Early versions crashed silently on corrupted PDFs. Users had no idea what went wrong.
Now:
javascript
try {
const pdf = await loadPDF(file);
} catch (error) {
if (error.name === 'InvalidPDFException') {
showUserMessage('This file appears to be corrupted. Try opening it in Adobe Reader first.');
} else if (error.name === 'PasswordProtectedException') {
showUserMessage('This PDF is password-protected. Remove the password and try again.');
} else {
showUserMessage('Something went wrong. Please try a different file.');
}
}
Clear error messages = fewer support emails.
3. Add Telemetry (Privacy-Friendly)
I deliberately avoided tracking file operations for privacy. But this means I don’t know which features are actually used.
What I’d add:
javascript
// Track feature usage (NOT file details)
analytics.track('feature_used', {
feature: 'compress', // Which button clicked
success: true, // Did it work
// Explicitly NOT tracking:
// - file names
// - file sizes
// - file contents
});
This would help me prioritize features without compromising privacy.
Lessons for Building Privacy-First Apps
1. Privacy by Architecture > Privacy by Policy
Don’t tell users you won’t misuse their data. Build systems where you can’t misuse their data.
If your architecture makes it impossible to access user data, you don’t need a 10-page privacy policy explaining how you promise not to.
2. “No Backend” is a Valid Architecture
We’re trained to think every app needs:
-
User authentication
-
Database
-
API server
-
Background jobs
But for many use cases, client-side processing is simpler, cheaper, and more private.
Ask: “Does this really need a server?”
3. WebAssembly is Ready for Production
Browser support is excellent (95%+ globally). Performance is incredible. Tooling has matured.
If you’re doing CPU-intensive work in JavaScript, consider compiling to WASM.
4. Users Care About Privacy (More Than You Think)
40% of PDFfreeEditor users specifically mentioned privacy in feedback:
-
“Finally, a tool I can trust with sensitive documents”
-
“Love that my files stay on my computer”
-
“This should be the standard for all PDF tools”
There’s a real market for privacy-first alternatives to popular tools.
The Results
After 6 months:
-
40,000+ total users
-
65,000+ PDFs processed
-
$0 spent on infrastructure
-
$0 spent on advertising
-
Featured on Product Hunt (#3 product of the day)
-
Front page of Hacker News
-
4.8/5 star average rating
How I grew it:
-
Product Hunt launch (8,000 visitors in 24 hours)
-
Hacker News post (4,000 visitors)
-
Reddit r/InternetIsBeautiful (2,500 visitors)
-
Word of mouth (ongoing)
Total marketing budget: $0
Good products that solve real problems market themselves.
The Code (Open Source Coming Soon)
I’m working on open-sourcing the core PDF processing engine.