<?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: Remolinator</title>
    <description>The latest articles on DEV Community by Remolinator (@remolinator).</description>
    <link>https://dev.to/remolinator</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%2F3672218%2F3f33bbc6-04ca-46b0-9d3c-78984a9784b9.jpg</url>
      <title>DEV Community: Remolinator</title>
      <link>https://dev.to/remolinator</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/remolinator"/>
    <language>en</language>
    <item>
      <title>How I built a Serverless PDF Editor with Vanilla JS (and $0 cloud bills)</title>
      <dc:creator>Remolinator</dc:creator>
      <pubDate>Sat, 20 Dec 2025 20:31:25 +0000</pubDate>
      <link>https://dev.to/remolinator/how-i-built-a-serverless-pdf-editor-with-vanilla-js-and-0-cloud-bills-554b</link>
      <guid>https://dev.to/remolinator/how-i-built-a-serverless-pdf-editor-with-vanilla-js-and-0-cloud-bills-554b</guid>
      <description>&lt;p&gt;We all have that moment. You need to merge two PDF contracts or compress a video file, and you search on Google. You find a tool, upload your file, and then... you wait.&lt;/p&gt;

&lt;p&gt;And then you wonder: &lt;em&gt;"Wait, did I just upload my tax return to a random server in a country I can't pronounce?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That paranoia is exactly why I built &lt;strong&gt;&lt;a href="https://www.dunetools.com/" rel="noopener noreferrer"&gt;DuneTools&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I wanted to challenge the status quo of heavy, server-dependent web apps. My goal was simple: build a suite of tools (PDF, Video, Image) that runs &lt;strong&gt;100% in the browser&lt;/strong&gt;. No uploads. No privacy risks. Just pure JavaScript.&lt;/p&gt;

&lt;p&gt;Here is how I built it (and why I ditched React for this one).&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Architecture: Client-Side is King 👑
&lt;/h2&gt;

&lt;p&gt;Most PDF tools work like this:&lt;br&gt;
&lt;code&gt;User -&amp;gt; Upload -&amp;gt; Server processes (Python/PHP) -&amp;gt; Download&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;DuneTools works like this:&lt;br&gt;
&lt;code&gt;User -&amp;gt; Browser (WASM + JS) processes -&amp;gt; Done&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;By using libraries like &lt;code&gt;pdf-lib&lt;/code&gt; and WebAssembly wrappers for FFMPEG, modern browsers are powerful enough to handle heavy media editing. This has two massive benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Privacy:&lt;/strong&gt; The file never leaves the user's device.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Cost:&lt;/strong&gt; My server costs are flat. Whether I have 10 users or 10,000, I don't pay for CPU processing time.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  2. Why Vanilla JS? (No React, No Vue) 🍦
&lt;/h2&gt;

&lt;p&gt;I know, I know. It's 2025. Why aren't you using Next.js?&lt;/p&gt;

&lt;p&gt;For a tool like this, &lt;strong&gt;performance is the feature&lt;/strong&gt;. I didn't want to ship 200KB of hydration scripts just to render a "Merge PDF" button.&lt;/p&gt;

&lt;h3&gt;
  
  
  My "Hybrid" Build Workflow
&lt;/h3&gt;

&lt;p&gt;Instead of a heavy JS framework, I use &lt;strong&gt;Python&lt;/strong&gt; as a static site generator:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; I write my logic in plain &lt;strong&gt;Vanilla JS&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; I use &lt;strong&gt;Python + Jinja2&lt;/strong&gt; templates to generate the HTML pages for each tool (SEO is crucial here).&lt;/li&gt;
&lt;li&gt; The Python script compiles everything into a static folder ready for deployment.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The result?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lighthouse Score:&lt;/strong&gt; 100/100 🟢&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load Time:&lt;/strong&gt; Instant.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. The Deployment: Vercel (Zero Config) ▲
&lt;/h2&gt;

&lt;p&gt;Since this is a client-side app with no backend database, I didn't want to manage servers, SSH keys, or Docker containers.&lt;/p&gt;

&lt;p&gt;I chose &lt;strong&gt;Vercel&lt;/strong&gt; for deployment. It fits perfectly with my "lazy developer" philosophy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Git Integration:&lt;/strong&gt; I push my generated static files to GitHub, and Vercel detects the change and deploys automatically.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Global CDN:&lt;/strong&gt; My static assets (JS/WASM) are cached on the edge, making the tool load instantly from anywhere in the world.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Cost:&lt;/strong&gt; Free. Since I'm not using Serverless Functions for processing (remember, it's all client-side), I stay comfortably within the free tier limits.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It’s the ultimate "set it and forget it" setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The Result: DuneTools 🏜️
&lt;/h2&gt;

&lt;p&gt;The project is now live. It includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PDF Tools:&lt;/strong&gt; Merge, Split, Compress.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Video:&lt;/strong&gt; Converter, GIF maker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Images:&lt;/strong&gt; Compression, resizing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s fast, it’s free, and most importantly, it’s &lt;strong&gt;private&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I'd love for you to roast my code or give me feedback on the UX.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Try it here:&lt;/strong&gt; &lt;a href="https://www.dunetools.com/" rel="noopener noreferrer"&gt;DuneTools.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(P.S. If you are an Indie Hacker looking for a privacy-first alternative to iLovePDF, I built this for you!)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3rl05vyi4q7bgt2jgj8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3rl05vyi4q7bgt2jgj8.jpg" alt=" " width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8yhq4t8j7znzah7m0c8x.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8yhq4t8j7znzah7m0c8x.jpg" alt=" " width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxt7mse7wrp0ydzwc926e.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxt7mse7wrp0ydzwc926e.jpg" alt=" " width="800" height="793"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>privacy</category>
    </item>
  </channel>
</rss>
