<?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: nikoo li</title>
    <description>The latest articles on DEV Community by nikoo li (@nikoo_li_06a1e02c73785324).</description>
    <link>https://dev.to/nikoo_li_06a1e02c73785324</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%2F4011917%2F0d49d2c8-60d1-4c96-a4c5-3949e7dd0585.png</url>
      <title>DEV Community: nikoo li</title>
      <link>https://dev.to/nikoo_li_06a1e02c73785324</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nikoo_li_06a1e02c73785324"/>
    <language>en</language>
    <item>
      <title>Processing 100MB PDFs in the Browser: The Performance Optimizations That Made TinyPDF Usable</title>
      <dc:creator>nikoo li</dc:creator>
      <pubDate>Tue, 28 Jul 2026 15:35:11 +0000</pubDate>
      <link>https://dev.to/nikoo_li_06a1e02c73785324/processing-100mb-pdfs-in-the-browser-the-performance-optimizations-that-made-tinypdf-usable-307f</link>
      <guid>https://dev.to/nikoo_li_06a1e02c73785324/processing-100mb-pdfs-in-the-browser-the-performance-optimizations-that-made-tinypdf-usable-307f</guid>
      <description>&lt;h1&gt;
  
  
  Processing 100MB PDFs in the Browser: The Performance Optimizations That Made TinyPDF Usable
&lt;/h1&gt;

&lt;p&gt;When I built TinyPDF (&lt;a href="https://tinypdf.cn/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=performance_optimization&amp;amp;utm_content=devto_performance_2026-07-28" rel="noopener noreferrer"&gt;https://tinypdf.cn/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=performance_optimization&amp;amp;utm_content=devto_performance_2026-07-28&lt;/a&gt;), I had one hard rule: no backend.&lt;/p&gt;

&lt;p&gt;Everything had to run in the browser. No file uploads, no servers, no costs. Just drag, drop, compress, download.&lt;/p&gt;

&lt;p&gt;But when I tested the first version with a real portfolio—88MB, 45 pages, full of high-res images—it froze the tab for 12 seconds.&lt;/p&gt;

&lt;p&gt;Here's what I changed to get that down to 2 seconds, without losing any features.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Use Web Workers for PDF Parsing (Don't Block the Main Thread)
&lt;/h2&gt;

&lt;p&gt;The first mistake: I ran PDF.js parsing directly on the main thread.&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="c1"&gt;// ❌ Bad: Blocks UI while parsing&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pdf&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;pdfjsLib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDocument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fix: Offload everything to a Web Worker. The main thread only handles user input and progress updates.&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="c1"&gt;// ✅ Good: Web Worker does the heavy lifting&lt;/span&gt;
&lt;span class="c1"&gt;// Main thread&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;worker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pdf-compressor.worker.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&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;process&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;arrayBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;targetSizeMB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onmessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;progress&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;updateProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;percent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;done&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;downloadBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: Tab stays responsive even with 100MB files.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Stream Image Processing (Don't Load All Pages Into Memory)
&lt;/h2&gt;

&lt;p&gt;Second mistake: I loaded every page into memory at once before processing.&lt;/p&gt;

&lt;p&gt;For a 45-page portfolio, that's 45 full-res images in memory simultaneously.&lt;/p&gt;

&lt;p&gt;The fix: Process one page at a time, and stream results to the output blob incrementally.&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="c1"&gt;// ✅ Good: Process one page, free memory, repeat&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;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;numPages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&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;page&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;pdf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&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;viewport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getViewport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Render page to canvas&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;canvasContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;viewport&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Compress and add to output&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;compressedImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;compressCanvas&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentQuality&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nf"&gt;addToOutputBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compressedImage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Clean up immediately (critical for 100MB files)&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;updateProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;numPages&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: Memory usage stays flat even for 100-page documents.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Binary Search for Target Size (Avoid Wasted Cycles)
&lt;/h2&gt;

&lt;p&gt;The core feature of TinyPDF is "compress to an exact target size in MB."&lt;/p&gt;

&lt;p&gt;My first approach: Start at 100% quality, keep dropping by 10% until we hit the target.&lt;/p&gt;

&lt;p&gt;That could mean 5-10 full passes over the document.&lt;/p&gt;

&lt;p&gt;The fix: Binary search on quality.&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="c1"&gt;// ✅ Good: Binary search finds optimal quality in ~5 steps&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;minQuality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;maxQuality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;bestQuality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;bestSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;Infinity&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;let&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;++&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;midQuality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;minQuality&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;maxQuality&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&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;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;estimateSizeAtQuality&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;midQuality&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;targetSizeBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// We're under target—see if we can go higher quality&lt;/span&gt;
    &lt;span class="nx"&gt;bestQuality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;midQuality&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;bestSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;minQuality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;midQuality&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Over target—need to go lower&lt;/span&gt;
    &lt;span class="nx"&gt;maxQuality&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;midQuality&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: Gets to optimal quality in 5 attempts, not 10+.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Early Termination for Impossible Targets
&lt;/h2&gt;

&lt;p&gt;If a user asks to compress a 50MB document of pure text to 1MB, that's impossible—there's no image data to drop.&lt;/p&gt;

&lt;p&gt;My first version would try anyway, wasting 30 seconds before failing.&lt;/p&gt;

&lt;p&gt;The fix: Calculate a theoretical minimum size first.&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="c1"&gt;// ✅ Good: Estimate minimum possible size before trying&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textVectorSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;estimateTextVectorSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pdf&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetSizeBytes&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;textVectorSize&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;showWarning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`That target size might be too small—this document has &lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;textVectorSize&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;MB of non-image content.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Result: User gets immediate feedback, no wasted processing.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;100MB file: 12s → 2s&lt;/li&gt;
&lt;li&gt;Tab stays responsive the whole time&lt;/li&gt;
&lt;li&gt;Memory doesn't balloon&lt;/li&gt;
&lt;li&gt;No backend, no uploads, no account needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try it: &lt;a href="https://tinypdf.cn/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=performance_optimization&amp;amp;utm_content=devto_performance_2026-07-28" rel="noopener noreferrer"&gt;https://tinypdf.cn/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=performance_optimization&amp;amp;utm_content=devto_performance_2026-07-28&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Performance optimization isn't about "making it fast." It's about:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Not blocking the main thread&lt;/strong&gt; (users notice freezes more than slowness)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cleaning up immediately&lt;/strong&gt; (memory is finite, especially on mobile)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoiding unnecessary work&lt;/strong&gt; (binary search &amp;gt; linear search)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setting expectations early&lt;/strong&gt; (tell users when a target might be impossible)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;What's the biggest performance win you've had in a browser-only app?&lt;/p&gt;

</description>
      <category>pdf</category>
    </item>
    <item>
      <title>Building a PDF compressor in 2 weeks — no backend, no uploads, just browser magic</title>
      <dc:creator>nikoo li</dc:creator>
      <pubDate>Sat, 25 Jul 2026 06:21:07 +0000</pubDate>
      <link>https://dev.to/nikoo_li_06a1e02c73785324/building-a-pdf-compressor-in-2-weeks-no-backend-no-uploads-just-browser-magic-37hk</link>
      <guid>https://dev.to/nikoo_li_06a1e02c73785324/building-a-pdf-compressor-in-2-weeks-no-backend-no-uploads-just-browser-magic-37hk</guid>
      <description></description>
      <category>webdev</category>
      <category>javascript</category>
      <category>react</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Stop Bloating Your GitHub Releases: Compress Project Docs to Exact Sizes</title>
      <dc:creator>nikoo li</dc:creator>
      <pubDate>Wed, 22 Jul 2026 02:47:00 +0000</pubDate>
      <link>https://dev.to/nikoo_li_06a1e02c73785324/stop-bloating-your-github-releases-compress-project-docs-to-exact-sizes-46ac</link>
      <guid>https://dev.to/nikoo_li_06a1e02c73785324/stop-bloating-your-github-releases-compress-project-docs-to-exact-sizes-46ac</guid>
      <description>&lt;p&gt;If you maintain an open-source project, you've probably hit this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You generate a 40-page contributor guide with diagrams → 18MB&lt;/li&gt;
&lt;li&gt;GitHub Releases has no hard limit, but clone times get slower for everyone&lt;/li&gt;
&lt;li&gt;Some mirror sites do have limits&lt;/li&gt;
&lt;li&gt;Your &lt;code&gt;git clone&lt;/code&gt; gets slower over time as PDFs bloat the repo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I built TinyPDF for designers first, but I've been using it for my own project docs too: type the target size in MB, and it compresses to that size while preserving layout and readability as much as possible.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tinypdf.cn/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=opensource_docs&amp;amp;utm_content=devto_opensource_2026-07-22" rel="noopener noreferrer"&gt;https://tinypdf.cn/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=opensource_docs&amp;amp;utm_content=devto_opensource_2026-07-22&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No account, no watermarks, 100% free, works entirely in your browser (no server uploads).&lt;/p&gt;

&lt;p&gt;Bonus: if you're using GitHub Actions, you could wire this into your release pipeline (the compression runs locally in-browser right now, but I'm thinking about a CLI version).&lt;/p&gt;

&lt;p&gt;What's the biggest PDF you've committed to a repo? Let's compare in the comments.&lt;/p&gt;

</description>
      <category>opensource</category>
    </item>
    <item>
      <title>Stop wasting time on PDF compression — one-click to your target size</title>
      <dc:creator>nikoo li</dc:creator>
      <pubDate>Mon, 20 Jul 2026 15:47:33 +0000</pubDate>
      <link>https://dev.to/nikoo_li_06a1e02c73785324/stop-wasting-time-on-pdf-compression-one-click-to-your-target-size-45c6</link>
      <guid>https://dev.to/nikoo_li_06a1e02c73785324/stop-wasting-time-on-pdf-compression-one-click-to-your-target-size-45c6</guid>
      <description>&lt;h1&gt;
  
  
  Stop wasting time on PDF compression — one-click to your target size
&lt;/h1&gt;

&lt;p&gt;Let's be honest: PDF compression shouldn't be a 15-minute task.&lt;/p&gt;

&lt;p&gt;You export your document, drag it into a compressor, pick a "high/medium/low" preset, download, check the size… and it's still over the limit. Repeat. Adjust settings. Repeat again.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://tinypdf.cn/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=efficiency_launch&amp;amp;utm_content=devto_oneclick_efficiency_2026-07-20" rel="noopener noreferrer"&gt;TinyPDF&lt;/a&gt; because this workflow was killing my productivity as a designer.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works (in 3 clicks)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Drop your PDF&lt;/li&gt;
&lt;li&gt;Type your target size in MB (2MB, 5MB, 10MB — whatever you need)&lt;/li&gt;
&lt;li&gt;Hit "Compress"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. No guessing presets, no back-and-forth.&lt;/p&gt;

&lt;p&gt;The tool automatically figures out how much to reduce image quality while preserving page count, page size, and layout. It keeps quality as high as possible while hitting your exact target.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this saves time
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No trial and error:&lt;/strong&gt; Set the size once, get it right first time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No account needed:&lt;/strong&gt; Just open the page and compress&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No ads, no watermarks:&lt;/strong&gt; Clean, fast, distraction-free&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Files stay local:&lt;/strong&gt; Processing happens in your browser — no uploads to a server&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Current limits (keeping it focused)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Desktop web only (mobile coming later)&lt;/li&gt;
&lt;li&gt;One file at a time, up to 100MB&lt;/li&gt;
&lt;li&gt;No batch mode yet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've ever found yourself wasting time on PDF compression, give it a try: &lt;a href="https://tinypdf.cn/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=efficiency_launch&amp;amp;utm_content=devto_oneclick_efficiency_2026-07-20" rel="noopener noreferrer"&gt;TinyPDF&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Would love to hear what workflows you'd automate next!&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>showdev</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The overlooked PDF problem in upload forms: fixed file-size limits</title>
      <dc:creator>nikoo li</dc:creator>
      <pubDate>Mon, 06 Jul 2026 02:16:20 +0000</pubDate>
      <link>https://dev.to/nikoo_li_06a1e02c73785324/the-overlooked-pdf-problem-in-upload-forms-fixed-file-size-limits-5b65</link>
      <guid>https://dev.to/nikoo_li_06a1e02c73785324/the-overlooked-pdf-problem-in-upload-forms-fixed-file-size-limits-5b65</guid>
      <description>&lt;p&gt;A small product detail I keep running into: upload forms often fail PDFs for a very boring reason.&lt;/p&gt;

&lt;p&gt;The file is not broken. The layout is fine. The user just needs the PDF to be under a fixed limit like 10 MB, 20 MB, or 25 MB.&lt;/p&gt;

&lt;p&gt;This happens a lot with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;portfolio submissions&lt;/li&gt;
&lt;li&gt;school or job applications&lt;/li&gt;
&lt;li&gt;client handoff portals&lt;/li&gt;
&lt;li&gt;internal forms&lt;/li&gt;
&lt;li&gt;support ticket attachments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The usual advice is “compress the PDF,” but that is a bit vague. In practice, the user does not always want maximum compression. They want a specific outcome:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make this PDF fit under the upload limit, then let me check whether it still looks acceptable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the workflow I built TinyPDF around:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tinypdf.cn/?utm_source=devto&amp;amp;utm_medium=developer_promo&amp;amp;utm_campaign=portfolio_upload_limits&amp;amp;utm_content=devto_upload_limit_workflow_20260706" rel="noopener noreferrer"&gt;https://tinypdf.cn/?utm_source=devto&amp;amp;utm_medium=developer_promo&amp;amp;utm_campaign=portfolio_upload_limits&amp;amp;utm_content=devto_upload_limit_workflow_20260706&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is intentionally narrow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload one PDF.&lt;/li&gt;
&lt;li&gt;Enter the target size in MB.&lt;/li&gt;
&lt;li&gt;Download the compressed result.&lt;/li&gt;
&lt;li&gt;Open the result and inspect it before submitting.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The interesting product lesson for me is that “PDF compression” is too broad a category. The sharper use case is the moment when an upload form rejects a file and the user has one concrete number to hit.&lt;/p&gt;

&lt;p&gt;If you build upload flows, what file-size limits do you usually set for PDFs? And do you explain the limit before or after the upload fails?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>showdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Compressing PDFs to a target file size instead of guessing quality presets</title>
      <dc:creator>nikoo li</dc:creator>
      <pubDate>Thu, 02 Jul 2026 11:19:40 +0000</pubDate>
      <link>https://dev.to/nikoo_li_06a1e02c73785324/compressing-pdfs-to-a-target-file-size-instead-of-guessing-quality-presets-1hoc</link>
      <guid>https://dev.to/nikoo_li_06a1e02c73785324/compressing-pdfs-to-a-target-file-size-instead-of-guessing-quality-presets-1hoc</guid>
      <description>&lt;p&gt;When a PDF is too large, the real requirement is often not “make it smaller.”&lt;/p&gt;

&lt;p&gt;It is usually more concrete:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the job portal says the file must be under 10 MB&lt;/li&gt;
&lt;li&gt;the school form rejects anything above 5 MB&lt;/li&gt;
&lt;li&gt;the client upload system has a 20 MB limit&lt;/li&gt;
&lt;li&gt;an email attachment fails at the last step&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why I built TinyPDF as a small target-size PDF compressor:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tinypdf.cn/" rel="noopener noreferrer"&gt;https://tinypdf.cn/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Instead of choosing “low / medium / high” compression and checking the result manually, the workflow is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upload one PDF&lt;/li&gt;
&lt;li&gt;Enter the target size in MB&lt;/li&gt;
&lt;li&gt;Let the tool try to compress under that limit&lt;/li&gt;
&lt;li&gt;Download the result&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The implementation goal is to preserve page count, page dimensions, and layout whenever possible. If the PDF is image-heavy and the target is very small, there is always a quality tradeoff, so the UI tries to keep that expectation clear.&lt;/p&gt;

&lt;p&gt;This is a narrow tool by design. It does not merge, split, edit, OCR, or sign PDFs. It only focuses on one annoying workflow: “this file needs to be under a specific size.”&lt;/p&gt;

&lt;p&gt;I am sharing it here because I would like feedback from people who deal with document uploads in real workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is target-size compression clearer than quality presets?&lt;/li&gt;
&lt;li&gt;What file limits do you see most often?&lt;/li&gt;
&lt;li&gt;What privacy details should be visible before upload?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>productivity</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
