DEV Community

sunshey
sunshey

Posted on

How to Build Batch PDF Processing with Vue 3 and pdf-lib

Batch processing PDFs sounds like a server-side job, but with pdf-lib and a little patience, you can build it entirely in the browser. This post walks through a minimal but production-ready batch PDF processor.

Why client-side batch?

Server-side batch processing works, but it introduces latency, bandwidth costs, and privacy risk. A browser-based batch processor:

  • Keeps files on the user's device
  • Processes files without uploading anything
  • Avoids server-side PDF processing entirely
  • Handles large batches without queueing

The stack

  • Vue 3 with Composition API
  • pdf-lib for PDF manipulation
  • JSZip for packaging results
  • Native File API for reading uploads

Minimal implementation

<script setup lang="ts">
import { ref } from 'vue'
import { PDFDocument } from 'pdf-lib'
import JSZip from 'jszip'

const files = ref<File[]>([])
const processing = ref(false)
const results = ref<Blob[]>([])

async function processBatch() {
  processing.value = true
  results.value = []

  try {
    for (const file of files.value) {
      const bytes = await file.arrayBuffer()
      const pdf = await PDFDocument.load(bytes)

      // Example: compress by removing metadata
      const modifiedBytes = await pdf.save({
        useObjectStreams: false,
        addDefaultPage: false,
        objectsPerTick: 50,
        maxIterations: 2,
      })

      results.value.push(new Blob([modifiedBytes], { type: 'application/pdf' }))
    }

    // Package all results into a ZIP
    const zip = new JSZip()
    results.value.forEach((blob, i) => {
      zip.file(`output_${i + 1}.pdf`, blob)
    })

    const zipBlob = await zip.generateAsync({ type: 'blob' })
    downloadBlob(zipBlob, 'batch-output.zip')
  } finally {
    processing.value = false
  }
}

function downloadBlob(blob: Blob, filename: string) {
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = filename
  a.click()
  URL.revokeObjectURL(url)
}
</script>
Enter fullscreen mode Exit fullscreen mode

The key is pdf.save({ useObjectStreams: false, objectsPerTick: 50 }). Setting useObjectStreams: false produces slightly larger files but is faster to save. objectsPerTick: 50 controls how many objects are processed per microtask, preventing the UI from freezing during large batches.

Handling large batches

Browser memory is the main constraint. For a production tool:

const MAX_FILES = 100
const MAX_TOTAL_SIZE = 500 * 1024 * 1024 // 500 MB
Enter fullscreen mode Exit fullscreen mode

Validate before processing. Show a progress indicator. And always wrap the save in a try/catch — one bad file should not crash the entire batch.

UX tips from a live tool

At en.sotool.top/batch-merge, we learned a few things from real users:

  1. Show a progress bar. Users want to know how far along they are.
  2. Allow cancellation. If a batch is taking too long, let users bail.
  3. Package results as ZIP. Nobody wants to download 50 separate files.
  4. Track events separately. Measure drop-off between upload, processing, and download.

Tracking the funnel

We use GA4 custom events:

onFileUpload(files)
onActionClick('batch-merge')
onCompleted({ file_count: files.length })
onDownload({ file_count: files.length })
Enter fullscreen mode Exit fullscreen mode

This lets us see exactly where users drop off.

Going further

For a simple batch processor, pdf-lib is enough. If you need OCR, bookmarks, or form flattening, you'll want a server-side component or a desktop tool.

Want to see the full source? The site is built in public at github.com/sunshey/pdf-tool.


If you need desktop-grade PDF editing — batch processing with advanced settings, OCR, or form management — check out Wondershare PDFelement.

Top comments (0)