<?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: Eduardo Keser</title>
    <description>The latest articles on DEV Community by Eduardo Keser (@eduardo_keser_69348b8e6a1).</description>
    <link>https://dev.to/eduardo_keser_69348b8e6a1</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4116625%2F2fe68f5a-8187-4130-bffd-9eb8eb09f988.jpg</url>
      <title>DEV Community: Eduardo Keser</title>
      <link>https://dev.to/eduardo_keser_69348b8e6a1</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eduardo_keser_69348b8e6a1"/>
    <language>en</language>
    <item>
      <title>Merging PDFs in the browser without uploading anything: what I learned building a free tool for Argentine paperwork</title>
      <dc:creator>Eduardo Keser</dc:creator>
      <pubDate>Mon, 05 Oct 2026 04:11:04 +0000</pubDate>
      <link>https://dev.to/eduardo_keser_69348b8e6a1/merging-pdfs-in-the-browser-without-uploading-anything-what-i-learned-building-a-free-tool-for-22k2</link>
      <guid>https://dev.to/eduardo_keser_69348b8e6a1/merging-pdfs-in-the-browser-without-uploading-anything-what-i-learned-building-a-free-tool-for-22k2</guid>
      <description>&lt;p&gt;Most "merge PDF" sites ask you to upload your files to a server. That's fine for a menu, but not for a scan of your ID or a payslip. When I built Trámite Listo (a free set of PDF tools for Argentine paperwork), I made one rule: &lt;strong&gt;the files never leave the user's device.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's how it works and what bit me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://pdf-lib.js.org/" rel="noopener noreferrer"&gt;pdf-lib&lt;/a&gt; to create and modify PDFs (merge, split, rotate, images to PDF, remove pages).&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mozilla.github.io/pdf.js/" rel="noopener noreferrer"&gt;pdf.js&lt;/a&gt; to read and render pages when I need to see them.&lt;/li&gt;
&lt;li&gt;JSZip to bundle several outputs into one download.&lt;/li&gt;
&lt;li&gt;jsdiff to compare two documents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No backend processes files. The site is static HTML and JS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Merging, in a few lines
&lt;/h2&gt;

&lt;p&gt;The core idea is to load each PDF, copy its pages into a new document, and save:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;PDFLib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PDFDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;PDFLib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PDFDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copyPages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPageIndices&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="nx"&gt;pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;           &lt;span class="c1"&gt;// Uint8Array&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/pdf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything happens in memory in the user's tab, and the download is a &lt;code&gt;Blob&lt;/code&gt; URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things that bit me
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Memory is the real limit.&lt;/strong&gt; Large PDFs are limited by the user's device, not by my server. I say so on the page ("works with files up to about 100 MB, depending on your device's memory") instead of promising more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encrypted or damaged PDFs.&lt;/strong&gt; &lt;code&gt;load&lt;/code&gt; can throw. I show a plain message instead of failing silently.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phones.&lt;/strong&gt; Many people in my country do paperwork from a phone, so the UI has to work with a file picker and without drag-and-drop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust has to be visible.&lt;/strong&gt; A line saying "100 % private: files are processed on your device" next to the upload box did more than any technical explanation.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Bonus: a deadline calculator with moving rules
&lt;/h2&gt;

&lt;p&gt;The same site has a calculator for legal deadlines that accounts for national and provincial holidays. The surprise: the code is easy, &lt;strong&gt;the data keeps moving.&lt;/strong&gt; A new decree can add a holiday overnight, so I cite the official source for every rule and re-check it regularly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it / feedback
&lt;/h2&gt;

&lt;p&gt;It's free, available in Spanish and English, and has no signup: &lt;a href="https://edydigital.online" rel="noopener noreferrer"&gt;https://edydigital.online&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I'd love feedback from anyone who has built client-side file tools: what limits did you hit, and how do you handle very large files? I wrote this post myself.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>privacy</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SaaSFactory: from app idea to a launch-ready SaaS</title>
      <dc:creator>Eduardo Keser</dc:creator>
      <pubDate>Wed, 09 Sep 2026 02:40:06 +0000</pubDate>
      <link>https://dev.to/eduardo_keser_69348b8e6a1/saasfactory-from-app-idea-to-a-launch-ready-saas-5h0c</link>
      <guid>https://dev.to/eduardo_keser_69348b8e6a1/saasfactory-from-app-idea-to-a-launch-ready-saas-5h0c</guid>
      <description>&lt;p&gt;Most app ideas do not fail because the founder lacks motivation. They fail because the path from idea to something launchable stays unclear for too long.&lt;/p&gt;

&lt;p&gt;SaaSFactory is a practical workspace for turning an app idea into a clearer product plan, an MVP path, a production checklist, and a monetization setup. It brings discovery, specification, build, authentication, data, payments, and launch readiness into one workflow.&lt;/p&gt;

&lt;p&gt;The goal is not to call a polished demo “finished”. The goal is to make the gaps visible before charging real customers.&lt;/p&gt;

&lt;p&gt;There is a free entry tier and a paid plan at $49/month. The product interface is currently in Spanish, and I am sharing it publicly to collect feedback from founders and builders.&lt;/p&gt;

&lt;p&gt;Try it here: &lt;a href="https://saasfactory-livid.vercel.app" rel="noopener noreferrer"&gt;https://saasfactory-livid.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What would you want checked before calling your own SaaS ready?&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>devbugsmash</category>
      <category>startup</category>
    </item>
  </channel>
</rss>
