<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: opzozi</title>
    <description>The latest articles on DEV Community by opzozi (@opzozi).</description>
    <link>https://dev.to/opzozi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3662405%2Fc3bb2441-d321-4845-9cdf-1186dd9f102d.png</url>
      <title>DEV Community: opzozi</title>
      <link>https://dev.to/opzozi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/opzozi"/>
    <language>en</language>
    <item>
      <title>Building a Serverless PDF Merger &amp; Editor using React and pdf-lib (No Backend!)</title>
      <dc:creator>opzozi</dc:creator>
      <pubDate>Mon, 22 Dec 2025 21:33:08 +0000</pubDate>
      <link>https://dev.to/opzozi/building-a-serverless-pdf-merger-editor-using-react-and-pdf-lib-no-backend-1f1l</link>
      <guid>https://dev.to/opzozi/building-a-serverless-pdf-merger-editor-using-react-and-pdf-lib-no-backend-1f1l</guid>
      <description>&lt;p&gt;I merge PDFs about twice a week. Not enough to pay for an Adobe subscription, but enough to be annoyed by online tools.&lt;/p&gt;

&lt;p&gt;You know the drill:&lt;/p&gt;

&lt;p&gt;Google "merge pdf free".&lt;/p&gt;

&lt;p&gt;Upload your private bank statement to a random server.&lt;/p&gt;

&lt;p&gt;Get hit with a "Daily Limit Reached" popup just when you need to download the result.&lt;/p&gt;

&lt;p&gt;I got tired of this. I realized that modern browsers and WebAssembly are fast enough to handle PDF manipulation client-side. We don't actually need a backend for this anymore.&lt;/p&gt;

&lt;p&gt;So, I built Simple VaultPDF.&lt;/p&gt;

&lt;p&gt;The Tech Stack&lt;br&gt;
I wanted to keep it lightweight and 100% offline.&lt;/p&gt;

&lt;p&gt;Framework: React (Vite)&lt;/p&gt;

&lt;p&gt;PDF Engine: pdf-lib (for merging/splitting) &amp;amp; pdf.js (for rendering)&lt;/p&gt;

&lt;p&gt;OCR: tesseract.js&lt;/p&gt;

&lt;p&gt;State: Just React hooks, kept it simple.&lt;/p&gt;

&lt;p&gt;The Code (How it works)&lt;br&gt;
The coolest part is that pdf-lib lets you manipulate binary data directly in the browser memory. Here is the core function that merges files without sending a single byte to a server:&lt;/p&gt;

&lt;p&gt;JavaScript&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { PDFDocument } from 'pdf-lib';

const mergeFiles = async (files) =&amp;gt; {
  const mergedPdf = await PDFDocument.create();

  for (const file of files) {
    const fileBuffer = await file.arrayBuffer();

    const pdf = await PDFDocument.load(fileBuffer);

    const copiedPages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());

    copiedPages.forEach((page) =&amp;gt; mergedPdf.addPage(page));
  }

  const pdfBytes = await mergedPdf.save();
  return pdfBytes;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why Local-First?&lt;br&gt;
Building it as a Chrome Extension instead of a website had huge benefits:&lt;/p&gt;

&lt;p&gt;Zero Server Costs: Since I don't process the files, I don't pay for AWS/Cloud functions.&lt;/p&gt;

&lt;p&gt;Privacy: Users don't have to trust me with their data, because the data never leaves their machine.&lt;/p&gt;

&lt;p&gt;Speed: No upload/download latency.&lt;/p&gt;

&lt;p&gt;The Result&lt;br&gt;
It's currently an MVP. It handles merging, reordering, and rotation perfectly. I also added basic OCR because why not.&lt;/p&gt;

&lt;p&gt;I just published it to the Chrome Web Store. If you are tired of paywalls or just want to see how pdf-lib performs in a real extension, give it a try.&lt;/p&gt;

&lt;p&gt;Link: &lt;a href="https://www.google.com/search?q=https://chromewebstore.google.com/detail/simple-vaultpdf/nefkedjebfockbphoninolplkhgpakoh%3Fhl%3Den%26utm_source%3Ddevto" rel="noopener noreferrer"&gt;Simple VaultPDF on Chrome Web Store&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me know if you find any bugs! I'm still optimizing the memory usage for large files.&lt;/p&gt;

&lt;p&gt;Happy coding!&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>chromeextension</category>
    </item>
    <item>
      <title>I hate WebP files. So I built a Chrome extension to kill them.</title>
      <dc:creator>opzozi</dc:creator>
      <pubDate>Tue, 16 Dec 2025 11:26:10 +0000</pubDate>
      <link>https://dev.to/opzozi/i-hate-webp-files-so-i-built-a-chrome-extension-to-kill-them-3cld</link>
      <guid>https://dev.to/opzozi/i-hate-webp-files-so-i-built-a-chrome-extension-to-kill-them-3cld</guid>
      <description>&lt;p&gt;Okay, look. I know WebP is technically superior. Smaller file sizes, better compression, Google loves it. Great for the web performance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But for me? It's a nightmare.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every time I try to save an image for a presentation or just to share a meme, it saves as &lt;code&gt;.webp&lt;/code&gt;. Then I try to open it in an older version of Photoshop? Error. Try to upload it to a CMS? "Invalid file format." Try to send it to a non-tech friend? "I can't open this."&lt;/p&gt;

&lt;p&gt;I got tired of googling "online webp to png converter," uploading my files to random shady servers, and hoping for the best.&lt;/p&gt;

&lt;p&gt;So, I decided to fix it myself.&lt;/p&gt;

&lt;h3&gt;
  
  
  The "Rage-Code" Solution
&lt;/h3&gt;

&lt;p&gt;I built a tiny Chrome extension called &lt;strong&gt;Simple Image Converter&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It does exactly one thing: adds a &lt;strong&gt;"Save Image as PNG"&lt;/strong&gt; button to your right-click menu.&lt;/p&gt;

&lt;p&gt;No "cloud processing." It just grabs the image, converts it locally, and hands you a PNG.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Update: I also added a "Copy Image as PNG" option to the clipboard, because saving files is sometimes too much effort for a quick Discord paste.)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Tech Bit (Manifest V3 + Offscreen API)
&lt;/h3&gt;

&lt;p&gt;If you've tried building Chrome extensions lately, you know Manifest V3 can be... let's say, &lt;em&gt;interesting&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Since we can't use background pages with DOM access anymore, I couldn't just throw the image onto a canvas in the background script like in the good old days.&lt;/p&gt;

&lt;p&gt;I had to use the &lt;code&gt;chrome.offscreen&lt;/code&gt; API. Basically, the extension spins up a hidden HTML document for a split second, draws the WebP image onto a canvas there, converts it to a PNG blob, and sends it back to the download manager.&lt;/p&gt;

&lt;p&gt;It sounds complicated, but it keeps everything:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Offline:&lt;/strong&gt; Your images never leave your machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast:&lt;/strong&gt; No server latency.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private:&lt;/strong&gt; I don't want your data.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  It's Open Source
&lt;/h3&gt;

&lt;p&gt;I just want this problem to go away for everyone else too.&lt;/p&gt;

&lt;p&gt;If you also have a burning hatred for WebP files (or just want to see how the Offscreen API works in practice), check it out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub Repo:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/opzozi/simple-image-converter" rel="noopener noreferrer"&gt;https://github.com/opzozi/simple-image-converter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chrome Web Store:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://chromewebstore.google.com/detail/simple-image-converter/clinbfiephmemllcffpddoabnknkaeki" rel="noopener noreferrer"&gt;https://chromewebstore.google.com/detail/simple-image-converter/clinbfiephmemllcffpddoabnknkaeki&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me know if it breaks (or if you have a better way to handle the Offscreen API, I'm all ears).&lt;/p&gt;

&lt;p&gt;Cheers,&lt;br&gt;
Opzozi&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>javascript</category>
      <category>chrome</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
