DEV Community

Cover image for I Was Tired of Uploading PDFs… So I Built My Own Tool
NOCODE STUDIO
NOCODE STUDIO

Posted on

I Was Tired of Uploading PDFs… So I Built My Own Tool

I didn't start by writing code. I started with a simple frustration. Every time I needed to split a PDF, the process felt wrong.

Upload file → wait → download → hope nothing breaks.

And the bigger issue:

Why am I uploading my files at all?

That didn't sit right with me, especially when dealing with sensitive files. Some of these PDFs contain sensitive data. Invoices, contracts, internal docs…

Things that shouldn’t leave my machine.

So I wanted to see if I could build a tool that runs entirely in the browser, keeping data private and the UX instantaneous.

The Breaking Point

One day I was working on a small SaaS idea that needed basic PDF processing.

Nothing fancy. Just:

  • Split pages
  • Extract a few sections

The first hurdle, how do you manipulate heavy PDF structures in a client-side environment? After testing a few libraries, I landed on pdf-lib.

import { PDFDocument } from "pdf-lib";

Why this choice?

Zero Backend: It runs natively in the browser environment.

Lightweight: Perfect for MVPs and fast-loading web apps.

Simple API: The mental model for copying and merging pages is very intuitive.


Defining the Input Logic

I wanted a UI that felt familiar to anyone who has ever hit "Print." Instead of complex checkboxes, I opted for a simple string input: 1, 2, 5-8.

The goal was to convert that string into a clean array of indices:

"1, 2, 5-8" → 0, 1, 4, 5, 6, 7.

Parsing Page Ranges

I wrote a small utility function to handle the string-to-array logic, ensuring it could handle both individual pages and ranges while filtering out duplicates via a Set.

export function parsePageRanges(input: string): number[] {
  const pages = new Set<number>();

  input.split(",").forEach((part) => {
    const trimmed = part.trim();

    if (trimmed.includes("-")) {
      let [start, end] = trimmed.split("-").map(Number);
      if (start > end) [start, end] = [end, start];

      for (let i = start; i <= end; i++) {
        pages.add(i - 1); 
      }
    } else {
      const num = Number(trimmed);
      if (!isNaN(num)) pages.add(num - 1);
    }
  });

  return [...pages];
}
Enter fullscreen mode Exit fullscreen mode

Loading the PDF

Next, we need to get the file into a format pdf-lib understands. Using arrayBuffer() allows us to handle the file locally without any multi-part form uploads.

const bytes = file instanceof File ? await file.arrayBuffer() : file;
const pdf = await PDFDocument.load(bytes);
const totalPages = pdf.getPageCount();
Enter fullscreen mode Exit fullscreen mode

Extracting and Exporting

This is where the "magic" happens. We create a blank PDF document, copy the selected pages from the source, and save it.

const indices = parsePageRanges(input);
const newPdf = await PDFDocument.create();

// Copy the selected pages from the source PDF
const copiedPages = await newPdf.copyPages(pdf, indices);

// Add them to the new document
copiedPages.forEach((p) => newPdf.addPage(p));

// Save and return the bytes
return await newPdf.save();
Enter fullscreen mode Exit fullscreen mode

Pros

Building this small utility changed how I think about SaaS architecture.

Privacy is a Feature:
Users love knowing their files never touch a server.

Architecture over Infrastructure:
Sometimes the best "system" is the one you don't build. By avoiding a backend, I avoided costs, security risks, and scaling issues.

Browser Power:
Modern Web APIs and libraries are significantly more capable of heavy lifting than we often give them credit for.

Cons

Because the processing happens on the user's machine, it may tipically:

Memory Limits: Extremely large PDFs (think 500MB+) might crash a mobile browser.

Client Performance: A low-end device will process the split slower than a dedicated server.

However, for 95% of real-world use cases, the speed and privacy gains far outweigh these edge cases.

Result

This has become a PDF Splitter utility in my development.

If you're building a full-scale SaaS, an internal dashboard, or a quick MVP, remember: You might not need a backend for that.

Next time you're designing a feature, don't ask "How do I build this system?" Ask "Can I avoid building the system entirely?"


Top comments (0)