Package Exports
- @quicktoolsone/pdf-compress
- @quicktoolsone/pdf-compress/workers/compression.worker.cjs
- @quicktoolsone/pdf-compress/workers/compression.worker.cjs.map
- @quicktoolsone/pdf-compress/workers/compression.worker.d.ts
- @quicktoolsone/pdf-compress/workers/compression.worker.js
- @quicktoolsone/pdf-compress/workers/compression.worker.js.map
- @quicktoolsone/pdf-compress/workers/merge.worker.cjs
- @quicktoolsone/pdf-compress/workers/merge.worker.cjs.map
- @quicktoolsone/pdf-compress/workers/merge.worker.d.ts
- @quicktoolsone/pdf-compress/workers/merge.worker.js
- @quicktoolsone/pdf-compress/workers/merge.worker.js.map
Readme
@quicktoolsone/pdf-compress
WebAssembly-based, fully client-side PDF compression library for modern web applications
Features
- 🔒 100% Client-Side: Zero network requests, complete privacy
- ⚡ WASM-Powered: Fast compression using pdfcpu via WebAssembly
- 📦 Three Presets: Lossless, Balanced, Max compression
- 🔄 Chunked Processing: Handle large PDFs (300+ pages) reliably
- 📊 Progress Callbacks: Real-time progress updates for UX integration
- 🛡️ Graceful Degradation: Automatic fallback to lighter presets on errors
- 🌐 Framework Agnostic: Works with React, Vue, Next.js, vanilla JS
Installation
npm install @quicktoolsone/pdf-compressThe pdf-lib dependency is automatically included.
Quick Start
import { compress } from '@quicktoolsone/pdf-compress';
// Load PDF file
const file = await fetch('document.pdf').then(r => r.arrayBuffer());
// Compress with balanced preset (recommended)
const result = await compress(file, {
preset: 'balanced',
onProgress: (event) => {
console.log(`${event.phase}: ${event.progress}%`);
}
});
// Result contains compressed PDF and statistics
console.log(`Original: ${result.stats.originalSize} bytes`);
console.log(`Compressed: ${result.stats.compressedSize} bytes`);
console.log(`Saved: ${result.stats.percentageSaved.toFixed(1)}%`);
// Download compressed PDF
const blob = new Blob([result.pdf], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'compressed.pdf';
a.click();Compression Presets
Lossless (5-30% reduction)
Optimizes PDF structure without quality loss. Best for documents with text and vectors.
const result = await compress(pdfBuffer, { preset: 'lossless' });
// or
import { compressLossless } from '@quicktoolsone/pdf-compress';
const result = await compressLossless(pdfBuffer);Balanced (30-70% reduction) ⭐ Recommended
Smart image compression with minimal quality impact. Preserves text and vectors.
const result = await compress(pdfBuffer, { preset: 'balanced' });
// or
import { compressBalanced } from '@quicktoolsone/pdf-compress';
const result = await compressBalanced(pdfBuffer);Max (60-90% reduction)
Aggressive compression with optional rasterization. May reduce image quality.
const result = await compress(pdfBuffer, { preset: 'max' });
// or
import { compressMax } from '@quicktoolsone/pdf-compress';
const result = await compressMax(pdfBuffer);Advanced Options
import { compress } from '@quicktoolsone/pdf-compress';
const result = await compress(pdfBuffer, {
preset: 'balanced',
// Pages per chunk (default: 10, auto-adjusts for mobile)
chunkSize: 10,
// Progress callback
onProgress: (event) => {
console.log(`${event.phase}: ${event.progress}%`);
if (event.estimatedTimeRemaining) {
console.log(`ETA: ${event.estimatedTimeRemaining}ms`);
}
},
// Custom WASM URL (optional, defaults to jsdelivr CDN)
wasmUrl: 'https://cdn.jsdelivr.net/npm/@quicktoolsone/pdf-compress@1.0.0/src/wasm/build/pdfcpu.wasm',
// Graceful degradation (default: true)
gracefulDegradation: true,
// Preserve metadata (default: true for lossless/balanced, false for max)
preserveMetadata: true,
// Override preset DPI (balanced/max only)
targetDPI: 150,
// Override JPEG quality 0-1 (balanced/max only)
jpegQuality: 0.65,
// Merge strategy: 'worker' (default) or 'main' thread
mergeStrategy: 'worker',
// Timeout in milliseconds (default: 300000 = 5 minutes)
timeout: 300000,
});Framework Examples
React
import { compress } from '@quicktoolsone/pdf-compress';
import { useState } from 'react';
function PDFCompressor() {
const [progress, setProgress] = useState(0);
const [result, setResult] = useState(null);
const handleCompress = async (file: File) => {
const buffer = await file.arrayBuffer();
const compressed = await compress(buffer, {
preset: 'balanced',
onProgress: (event) => setProgress(event.progress)
});
setResult(compressed);
// Download
const blob = new Blob([compressed.pdf], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'compressed.pdf';
a.click();
};
return (
<div>
<input
type="file"
accept="application/pdf"
onChange={(e) => e.target.files?.[0] && handleCompress(e.target.files[0])}
/>
{progress > 0 && <progress value={progress} max={100} />}
{result && <p>Saved {result.stats.percentageSaved.toFixed(1)}%</p>}
</div>
);
}Next.js (App Router)
'use client'
import { compress } from '@quicktoolsone/pdf-compress';
export default function CompressPage() {
async function handleCompress(file: File) {
const buffer = await file.arrayBuffer();
const result = await compress(buffer, { preset: 'balanced' });
// Download
const blob = new Blob([result.pdf], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
window.open(url);
}
return (
<input
type="file"
accept="application/pdf"
onChange={(e) => e.target.files?.[0] && handleCompress(e.target.files[0])}
/>
);
}Vanilla JavaScript
<!DOCTYPE html>
<html>
<head>
<title>PDF Compressor</title>
</head>
<body>
<input type="file" id="pdfInput" accept="application/pdf">
<progress id="progress" value="0" max="100"></progress>
<div id="result"></div>
<script type="module">
import { compress } from '@quicktoolsone/pdf-compress';
document.getElementById('pdfInput').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
const buffer = await file.arrayBuffer();
const progress = document.getElementById('progress');
const result = await compress(buffer, {
preset: 'balanced',
onProgress: (event) => {
progress.value = event.progress;
}
});
document.getElementById('result').textContent =
`Saved ${result.stats.percentageSaved.toFixed(1)}%`;
// Download
const blob = new Blob([result.pdf], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'compressed.pdf';
a.click();
});
</script>
</body>
</html>How It Works
@quicktools/pdf-compress uses a chunked processing approach for reliability and memory efficiency:
- Validation: Checks if input is a valid PDF
- Chunking: Splits PDF into chunks (default: 10 pages)
- Worker Processing: Each chunk is compressed in a separate worker
- Worker Restart: Workers terminate after each chunk to free memory
- Merging: Compressed chunks are merged into final PDF
- Graceful Degradation: Falls back to lighter presets on errors
This architecture enables processing of 300-1000+ page PDFs even on mobile devices.
Browser Support
- Chrome/Edge 90+
- Firefox 89+
- Safari 15+
- Mobile: iOS 15+, Android Chrome 90+
Requirements: WebAssembly, Web Workers, ArrayBuffer support
Performance
Compression Ratios (Expected)
| Preset | Expected Savings | Processing Speed |
|---|---|---|
| Lossless | 5-30% | ~50 pages/sec |
| Balanced | 30-70% | ~30 pages/sec |
| Max | 60-90% | ~20 pages/sec |
Performance varies based on PDF content, device, and page complexity
Memory Usage
- Desktop: ~200MB per worker instance
- Mobile: ~100MB per worker instance
- Chunk Size: Auto-adjusts based on device and file size
Error Handling
import { compress, CompressionError } from '@quicktoolsone/pdf-compress';
try {
const result = await compress(pdfBuffer, { preset: 'max' });
} catch (error) {
if (error instanceof CompressionError) {
console.error(`Failed: ${error.message}`);
console.log(`Attempted preset: ${error.attemptedPreset}`);
console.log(`Original size: ${error.originalSize}`);
}
}With graceful degradation enabled (default), compression will automatically fall back to lighter presets:
Max fails → Balanced → Lossless → Return original PDFDevelopment Status
✅ v1.0 - MVP (Current)
- Three compression presets
- Chunked processing with worker restart
- Progress callbacks
- CDN WASM loading
- Basic error handling
- Graceful degradation
🚧 Upcoming Features
v1.1 - Enhanced UX
- Compression reports (detailed savings breakdown)
- Configurable chunk size via API
- Metadata preservation options
- Batch processing API
v2.0 - Advanced Features
- Multi-threaded compression (parallel chunks)
- Streaming PDF input/output
- Custom compression profiles
- Rasterization quality controls
Important Notes
WASM Module
The library automatically loads the pdfcpu WASM module from jsdelivr CDN when you call the compression functions. No additional setup is required - it just works!
CDN Details:
- Default URL:
https://cdn.jsdelivr.net/npm/@quicktoolsone/pdf-compress@latest/src/wasm/build/pdfcpu.wasm - WASM size: ~14 MB (served with gzip compression by jsdelivr)
- First load: Downloads from CDN and caches in browser
- Subsequent loads: Uses browser cache
Custom CDN (optional): If you want to self-host the WASM files or use a different CDN:
import { compress } from '@quicktoolsone/pdf-compress';
const result = await compress(pdfBuffer, {
preset: 'balanced',
wasmUrl: 'https://your-cdn.com/pdfcpu.wasm',
wasmExecUrl: 'https://your-cdn.com/wasm_exec.js'
});Building WASM from source: To build the WASM module yourself:
cd wasm-build
./build.shSee wasm-build/README.md for detailed build instructions.
Limitations
- WASM loading requires internet connection on first use (cached afterward)
- Large files (>500 pages) may take several minutes to process
- Mobile devices have stricter memory limits
- Text becomes non-searchable if rasterization is enabled (max preset)
License
MIT License - see LICENSE file
This software includes pdfcpu, licensed under Apache 2.0.
Contributing
Contributions welcome! Please read CONTRIBUTING.md for guidelines.
Acknowledgments
- pdfcpu - Excellent PDF library in Go
- pdf-lib - PDF manipulation in JavaScript
- Go team for WebAssembly support