<?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: Chris Howe</title>
    <description>The latest articles on DEV Community by Chris Howe (@chris_howe).</description>
    <link>https://dev.to/chris_howe</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%2F3556974%2Fcfad7c8c-1e3d-44f8-a8fb-7def84c0bd49.png</url>
      <title>DEV Community: Chris Howe</title>
      <link>https://dev.to/chris_howe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chris_howe"/>
    <language>en</language>
    <item>
      <title>Why PDF Size Should Be Checked Before It Breaks Your Workflow</title>
      <dc:creator>Chris Howe</dc:creator>
      <pubDate>Wed, 14 Oct 2026 09:10:06 +0000</pubDate>
      <link>https://dev.to/chris_howe/why-pdf-size-should-be-checked-before-it-breaks-your-workflow-5152</link>
      <guid>https://dev.to/chris_howe/why-pdf-size-should-be-checked-before-it-breaks-your-workflow-5152</guid>
      <description>&lt;p&gt;A document workflow often looks simple:&lt;/p&gt;

&lt;p&gt;Create a document → Export as PDF → Upload or send it.&lt;/p&gt;

&lt;p&gt;But in many real-world scenarios, the final step is where problems appear.&lt;/p&gt;

&lt;p&gt;A report is finished. A contract has been reviewed. A presentation has been exported.&lt;/p&gt;

&lt;p&gt;Then someone tries to upload it and gets an error:&lt;/p&gt;

&lt;p&gt;“File size exceeds the limit.”&lt;/p&gt;

&lt;p&gt;At this point, the document itself is already complete. The problem is not the content, but the way the file needs to move through different systems.&lt;/p&gt;

&lt;p&gt;This is why PDF file size management should be considered part of the document workflow, not something to solve at the very end.&lt;/p&gt;




&lt;h2&gt;
  
  
  PDF size problems usually appear too late
&lt;/h2&gt;

&lt;p&gt;Most people don't think about file size while creating documents.&lt;/p&gt;

&lt;p&gt;When writing a report, adding images, or scanning pages, the priority is usually content quality.&lt;/p&gt;

&lt;p&gt;A high-resolution image makes a presentation look better.&lt;/p&gt;

&lt;p&gt;A scanned document keeps more details.&lt;/p&gt;

&lt;p&gt;A complete report contains all necessary information.&lt;/p&gt;

&lt;p&gt;But all these choices increase the final PDF size.&lt;/p&gt;

&lt;p&gt;The issue is that many platforms have strict upload limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Email services limit attachment sizes.&lt;/li&gt;
&lt;li&gt;Online forms reject oversized documents.&lt;/li&gt;
&lt;li&gt;Collaboration platforms may slow down when processing large files.&lt;/li&gt;
&lt;li&gt;Cloud uploads take longer with bigger files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The workflow usually fails after the important work has already been completed.&lt;/p&gt;




&lt;h2&gt;
  
  
  A better approach: treat PDF size as a workflow checkpoint
&lt;/h2&gt;

&lt;p&gt;In software development, many teams use validation steps before deployment.&lt;/p&gt;

&lt;p&gt;Code is tested before release.&lt;/p&gt;

&lt;p&gt;Data is checked before entering production.&lt;/p&gt;

&lt;p&gt;Files can follow a similar approach.&lt;/p&gt;

&lt;p&gt;Instead of waiting until the final upload fails, PDF size can become an early checkpoint:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create the document.&lt;/li&gt;
&lt;li&gt;Export it as PDF.&lt;/li&gt;
&lt;li&gt;Check the file size.&lt;/li&gt;
&lt;li&gt;Compress if necessary.&lt;/li&gt;
&lt;li&gt;Continue sharing or uploading.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This simple step can prevent unnecessary interruptions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why PDF compression is often the easiest solution
&lt;/h2&gt;

&lt;p&gt;Manually reducing PDF size can be complicated.&lt;/p&gt;

&lt;p&gt;Users may need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adjust image quality;&lt;/li&gt;
&lt;li&gt;remove unnecessary elements;&lt;/li&gt;
&lt;li&gt;export the document again;&lt;/li&gt;
&lt;li&gt;compare different versions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For occasional users, this process takes too much time.&lt;/p&gt;

&lt;p&gt;A PDF compression tool simplifies the workflow by handling file optimization automatically.&lt;/p&gt;

&lt;p&gt;For example, PDFCompress is a free online PDF compressor designed for users who need to reduce PDF file size quickly without installing additional software.&lt;/p&gt;

&lt;p&gt;The process is straightforward:&lt;/p&gt;

&lt;p&gt;Upload a PDF file → compress the document → download the optimized version.&lt;/p&gt;

&lt;p&gt;This makes it easier to prepare documents for email attachments, online submissions, and file sharing.&lt;/p&gt;




&lt;h2&gt;
  
  
  File optimization is not only about smaller files
&lt;/h2&gt;

&lt;p&gt;When people think about compression, they often focus only on reducing size.&lt;/p&gt;

&lt;p&gt;But in practice, the bigger goal is improving document usability.&lt;/p&gt;

&lt;p&gt;A smaller PDF means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;faster uploads;&lt;/li&gt;
&lt;li&gt;easier sharing;&lt;/li&gt;
&lt;li&gt;fewer platform restrictions;&lt;/li&gt;
&lt;li&gt;smoother collaboration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best optimization is not simply making a file smaller.&lt;/p&gt;

&lt;p&gt;It is making the file easier to use in the environment where it needs to work.&lt;/p&gt;




&lt;h2&gt;
  
  
  Small workflow improvements create better experiences
&lt;/h2&gt;

&lt;p&gt;Modern digital workflows are built from many small steps.&lt;/p&gt;

&lt;p&gt;A document is created.&lt;/p&gt;

&lt;p&gt;A file is exported.&lt;/p&gt;

&lt;p&gt;A platform processes it.&lt;/p&gt;

&lt;p&gt;A user downloads or shares it.&lt;/p&gt;

&lt;p&gt;Any single step can become a bottleneck.&lt;/p&gt;

&lt;p&gt;PDF size is one of those problems that seems minor until it blocks the entire process.&lt;/p&gt;

&lt;p&gt;By adding file size checks and compression into the workflow, teams and individuals can avoid unnecessary delays.&lt;/p&gt;

&lt;p&gt;Tools like &lt;a href="https://pdfcompress.ai/" rel="noopener noreferrer"&gt;PDFCompress&lt;/a&gt; represent a simple idea:&lt;/p&gt;

&lt;p&gt;Good productivity tools do not always automate the biggest tasks.&lt;/p&gt;

&lt;p&gt;Sometimes, they remove the small obstacles that interrupt important work.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>pdf</category>
    </item>
    <item>
      <title>Build a PDF Size Gate Before Your Document Pipeline Hits “Send”</title>
      <dc:creator>Chris Howe</dc:creator>
      <pubDate>Tue, 13 Oct 2026 10:19:13 +0000</pubDate>
      <link>https://dev.to/chris_howe/build-a-pdf-size-gate-before-your-document-pipeline-hits-send-128d</link>
      <guid>https://dev.to/chris_howe/build-a-pdf-size-gate-before-your-document-pipeline-hits-send-128d</guid>
      <description>&lt;p&gt;The report rendered perfectly. The upload failed anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug was not in the report
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A successful build with an unusable artifact
&lt;/h3&gt;

&lt;p&gt;The job had done everything we asked of it. It queried the database, rendered charts, assembled a 126-page PDF, uploaded the artifact to storage, and marked the run green. Ten minutes later, someone tried to attach the report to a client portal and got a wonderfully unhelpful message: “File exceeds maximum size.”&lt;/p&gt;

&lt;p&gt;The PDF was 38.7 MB. The portal allowed 20 MB.&lt;/p&gt;

&lt;p&gt;Nothing in our pipeline considered that a failure because we had defined success too narrowly. We tested whether the file existed and whether it could be opened. We did not test whether it could complete the next step in the user’s workflow. The document was technically valid and operationally useless.&lt;/p&gt;

&lt;p&gt;That incident changed the way I think about generated PDFs. A PDF is not finished when the renderer exits with code zero. It is finished when the intended destination accepts it and the reader can still use it. File size belongs in the acceptance criteria, just like page count, content checks, and render quality.&lt;/p&gt;

&lt;h3&gt;
  
  
  Treat size as an interface contract
&lt;/h3&gt;

&lt;p&gt;Most document pipelines have an implicit interface at the end. It might be an email gateway, a grant application, a learning management system, a support ticket, or a customer portal. That interface has constraints even when your report generator does not know about them.&lt;/p&gt;

&lt;p&gt;The simplest improvement is to make the size budget explicit. Instead of generating a PDF and hoping for the best, define a delivery profile with a maximum number of bytes and a small safety margin. The margin matters because platforms do not always describe units consistently, and some wrap attachments with additional metadata.&lt;/p&gt;

&lt;p&gt;A 20 MB destination should not produce a 19.99 MB target. I would rather set the internal budget to 18 MB and leave room for disagreement about decimal megabytes, binary mebibytes, or a portal’s own processing overhead. This is not glamorous engineering, but neither is reopening a release five minutes before a deadline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep the master separate from the delivery copy
&lt;/h3&gt;

&lt;p&gt;Compression should create a derivative artifact. It should not overwrite the source PDF.&lt;/p&gt;

&lt;p&gt;The master is the closest representation of what the renderer produced. It is useful for archival, future exports, and debugging. The delivery copy is optimized for one channel with one size budget. If requirements change, you should be able to create a new delivery copy from the master instead of compressing an already compressed file again.&lt;/p&gt;

&lt;p&gt;Repeated compression is especially risky for image-heavy documents. Every generation may introduce another round of downsampling or lossy encoding. The file keeps getting smaller, but the relationship between size and visible quality becomes harder to reason about. A clean master gives the pipeline a stable starting point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a small gate before adding a big system
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Measure bytes, not a formatted string
&lt;/h3&gt;

&lt;p&gt;The first version of a size gate can be deliberately boring. In Node.js, the filesystem already gives us the exact byte count. We can compare it with a configured budget and return a structured result that the rest of the pipeline can understand.&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;stat&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&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;MiB&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="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;checkPdfBudget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;budgetMiB&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;file&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;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&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;limitBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;budgetMiB&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;MiB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;sizeMiB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;file&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;/&lt;/span&gt; &lt;span class="nx"&gt;MiB&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;2&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="nx"&gt;budgetMiB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;withinBudget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;file&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;limitBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;excessBytes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&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;file&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;-&lt;/span&gt; &lt;span class="nx"&gt;limitBytes&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;This function avoids making presentation strings part of the decision. The comparison happens in bytes; &lt;code&gt;sizeMiB&lt;/code&gt; exists only for logs and human-readable reports. That distinction prevents a rounded value such as &lt;code&gt;20.00 MB&lt;/code&gt; from hiding the fact that the file is a few kilobytes over the real threshold.&lt;/p&gt;

&lt;p&gt;The gate should run immediately after rendering and before distribution. If the file fits, the pipeline can continue. If it does not, the result should enter a compression path or stop with an actionable message. “Generated successfully” is not actionable. “Generated 38.7 MiB; delivery budget is 18 MiB” is.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make the result visible in CI
&lt;/h3&gt;

&lt;p&gt;A command-line wrapper turns the check into something CI can enforce. The script can print machine-readable JSON and use the process exit code to express the outcome.&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="cp"&gt;#!/usr/bin/env node
&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;checkPdfBudget&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./pdf-budget.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[,&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;budget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;18&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Usage: node check-pdf.js &amp;lt;file.pdf&amp;gt; [budgetMiB]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&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="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;checkPdfBudget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;budget&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;withinBudget&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exitCode&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the document build has a clear boundary. A scheduled report can fail before anyone emails it. A pull request that changes charts or embedded assets can reveal a size regression. A local developer can run the same command that CI runs. That consistency is more valuable than a sophisticated dashboard nobody trusts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do not confuse a size regression with a rendering failure
&lt;/h3&gt;

&lt;p&gt;The gate should classify the problem accurately. A PDF that is too large is not corrupt, and a PDF that is corrupt is not merely oversized. Keeping these outcomes separate makes retries and alerts much more useful.&lt;/p&gt;

&lt;p&gt;For example, a renderer failure may require inspecting HTML, fonts, or browser logs. A budget failure may indicate that a new image was embedded at full camera resolution, a font subset stopped working, or a chart library started rasterizing at a larger scale. The artifact exists in both cases, but the investigation path is different.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand what made the PDF heavy
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A PDF is a container, not a screenshot
&lt;/h3&gt;

&lt;p&gt;It is tempting to describe PDF compression as turning down a single quality knob. In reality, a PDF may contain text streams, embedded fonts, vector paths, raster images, forms, metadata, and duplicated resources. Scanned documents are often dominated by page-sized images. A generated financial report may contain searchable text and vector charts but still carry several large logos or screenshots.&lt;/p&gt;

&lt;p&gt;That structure affects what compression can achieve. Recompressing images can produce a dramatic reduction in a scanned brochure. The same operation may barely change a mostly textual report. Aggressively rasterizing every page can shrink some files, but it may also remove searchable text and make small type less readable. A smaller byte count is not automatically an optimization.&lt;/p&gt;

&lt;p&gt;Before choosing a response, I like to gather a few cheap signals with Poppler tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pdfinfo build/report-master.pdf
pdffonts build/report-master.pdf
pdftotext build/report-master.pdf - | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'[:space:]'&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt;
pdfimages &lt;span class="nt"&gt;-list&lt;/span&gt; build/report-master.pdf | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s1"&gt;'1,20p'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pdfinfo&lt;/code&gt; gives the page count and basic metadata. &lt;code&gt;pdffonts&lt;/code&gt; tells us whether fonts are embedded. &lt;code&gt;pdftotext&lt;/code&gt; provides a rough signal for a usable text layer. &lt;code&gt;pdfimages -list&lt;/code&gt; helps reveal large raster assets. None of these commands is a perfect classifier by itself, but together they narrow the problem quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sample the pages that are most likely to fail
&lt;/h3&gt;

&lt;p&gt;A document can be mixed. The first fifty pages may be clean text, followed by a scanned appendix. Checking only the cover creates false confidence. A useful validation set should include a normal text page, a chart-heavy page, a table with small type, a photo-heavy page, and at least one appendix page when those layouts exist.&lt;/p&gt;

&lt;p&gt;This is one place where a short manifest helps. The report generator already knows which templates created which sections, so it can emit representative page numbers alongside the PDF. If that is not possible, the team can maintain a small set of known-risk pages in the delivery profile.&lt;/p&gt;

&lt;p&gt;The goal is not pixel-perfect visual testing of every page. The goal is to make quality checks intentional. Randomly opening page one after compression is not a test plan; it is a ritual.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choose the compression path deliberately
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Automate the stable path, keep an escape hatch
&lt;/h3&gt;

&lt;p&gt;If your application produces the same document type every day, automated compression can make sense. You can run a known tool with versioned parameters, generate a candidate, validate it, and retain the original if the candidate is larger or fails checks. The compression policy becomes code and can evolve with the template.&lt;/p&gt;

&lt;p&gt;Not every team needs that system on day one. Some PDFs are created manually, some failures are rare, and some deadlines arrive before a new service can pass review. In those cases, a browser workflow is a reasonable operational escape hatch, provided the output still goes through the same quality gate.&lt;/p&gt;

&lt;p&gt;For an occasional oversized delivery, I use &lt;a href="https://pdfcompress.ai/" rel="noopener noreferrer"&gt;PDFCompress&lt;/a&gt; to create the derivative copy. It accepts PDFs directly in the browser, offers high, medium, and low compression modes, supports files up to 1 GB, and can process as many as five files in one batch. The result is available for immediate download without a watermark. That makes it useful when a teammate is on a locked-down machine or even a phone, but it does not remove the need to record the selected mode and validate the result.&lt;/p&gt;

&lt;p&gt;The important design decision is not “online tool versus command line.” It is whether the workflow has a source artifact, a named compression policy, and a testable candidate. A manual step can be disciplined. An automated step can still be careless.&lt;/p&gt;

&lt;h3&gt;
  
  
  Start with the least destructive mode that meets the budget
&lt;/h3&gt;

&lt;p&gt;When compression offers several levels, begin with the option that preserves the most quality. If the output fits the delivery budget, stop. If it does not, move to the next level and compare again. This sounds slower than selecting the strongest setting immediately, but it often avoids unnecessary damage and gives the team evidence about the document’s real compression headroom.&lt;/p&gt;

&lt;p&gt;Store the result of each attempt as data, not folklore. Record input bytes, output bytes, selected mode, duration, page count, and validation status. Over time, the pipeline will reveal patterns. Perhaps the marketing report nearly always passes on a medium setting, while scanned field reports need stronger compression. Those patterns can later justify automation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reject a candidate that does not improve the artifact
&lt;/h3&gt;

&lt;p&gt;Compression is not guaranteed to help every file. A PDF may already contain optimized images and compressed object streams. Processing it again can produce almost no saving, or occasionally a larger output. The pipeline should compare candidates and refuse to replace the delivery copy when there is no meaningful benefit.&lt;/p&gt;

&lt;p&gt;A simple savings calculation is enough:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;compressionSummary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;outputBytes&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;savedBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inputBytes&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;outputBytes&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;savedPercent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;inputBytes&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="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;savedBytes&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;inputBytes&lt;/span&gt;&lt;span class="p"&gt;)&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;inputBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;outputBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;savedBytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;savedPercent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;savedPercent&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="na"&gt;improved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;outputBytes&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;inputBytes&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;This protects against a surprisingly common workflow bug: naming the latest output &lt;code&gt;compressed.pdf&lt;/code&gt; and assuming that the name proves the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validate the delivery copy, not just the process
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Compare invariants before looking at pixels
&lt;/h3&gt;

&lt;p&gt;The first checks are structural. The output should open, retain the expected page count, and remain under the configured byte budget. If the workflow depends on text search or copy and paste, text extraction should still return plausible content. Links, bookmarks, and forms should be tested when they matter to the destination.&lt;/p&gt;

&lt;p&gt;These are not universal requirements. A scanned handout may never have had selectable text. A print-only brochure may not need internal links. Validation should reflect the artifact’s actual contract, which is why a delivery profile is more useful than one global “PDF passed” flag.&lt;/p&gt;

&lt;h3&gt;
  
  
  Render representative pages for visual review
&lt;/h3&gt;

&lt;p&gt;Structural tests cannot tell you whether a chart label turned muddy or a thin grid line disappeared. Render the known-risk pages to images and compare them at a consistent resolution. The first version of this check can be manual; the important part is that reviewers look at the same pages every time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; build/preview

pdftoppm &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-f&lt;/span&gt; 8 &lt;span class="nt"&gt;-l&lt;/span&gt; 8 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-r&lt;/span&gt; 144 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-png&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  build/report-delivery.pdf &lt;span class="se"&gt;\&lt;/span&gt;
  build/preview/page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Automated image diffs can come later, but they need sensible thresholds. Compression changes pixels by design, so a strict byte-for-byte image comparison will produce noise. Start by checking legibility, missing content, unexpected rotation, color banding, and obvious blocking around small text.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test the real destination once
&lt;/h3&gt;

&lt;p&gt;The final integration test is the one we skipped in the opening story: submit the file to the actual destination. Local size checks cannot discover every portal rule. The receiver may impose its own filename restrictions, MIME checks, timeouts, or preview conversion.&lt;/p&gt;

&lt;p&gt;For recurring pipelines, this may be a staging endpoint or a documented manual release check rather than an automated production upload. Either way, the destination should appear in the definition of done. A green renderer and a green compression job are still intermediate states.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gate changes team behavior
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fail early with a useful explanation
&lt;/h3&gt;

&lt;p&gt;Once the size budget became visible in our pipeline, the conversation changed. Instead of discovering the problem at the upload screen, we saw which build introduced the regression. Instead of asking someone to “make it smaller,” we could say how far over budget it was and which pages or assets had changed.&lt;/p&gt;

&lt;p&gt;That is the real benefit of a PDF size gate. It turns a last-minute human surprise into an observable property of the artifact. Compression remains a tradeoff, but it becomes a tradeoff the team can measure, review, and repeat.&lt;/p&gt;

&lt;p&gt;The implementation can begin with a dozen lines of Node.js and a written validation routine. Add automation only when the document volume and failure rate justify it. Whatever tools you choose, preserve the master, generate a delivery copy, and test the copy against the channel it is meant to cross.&lt;/p&gt;

&lt;p&gt;A PDF pipeline should not celebrate merely because it produced a PDF. It should celebrate when the document arrives, opens, and still says what it was meant to say.&lt;/p&gt;

</description>
      <category>pdf</category>
      <category>ai</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Built a Free Online Video Compressor in 2 Weeks</title>
      <dc:creator>Chris Howe</dc:creator>
      <pubDate>Fri, 10 Oct 2025 04:12:12 +0000</pubDate>
      <link>https://dev.to/chris_howe/i-built-a-free-online-video-compressor-in-2-weeks-49id</link>
      <guid>https://dev.to/chris_howe/i-built-a-free-online-video-compressor-in-2-weeks-49id</guid>
      <description>&lt;p&gt;Hi everyone! &lt;br&gt;
Over the past two weeks, I built a small side project called VideoCompress&lt;br&gt;
 — a fast, free, and easy-to-use online video compressor with no watermark or usage limits.&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;The Problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are plenty of “free” online video compressors, but most of them come with frustrating limitations — watermarks, file size limits, or hidden paywalls.&lt;br&gt;
I wanted something simple: just upload → compress → download — no distractions, no tricks.&lt;/p&gt;

&lt;p&gt;🧱 &lt;strong&gt;The Tech Stack&lt;/strong&gt;&lt;br&gt;
The project is built with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontend: Nuxt, for static site generation and an ultra-fast UI.&lt;/li&gt;
&lt;li&gt;Backend: FastAPI, for handling API requests.&lt;/li&gt;
&lt;li&gt;Video Processing: FFmpeg, running on the server for efficient compression.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Nuxt app is completely static — deployed on a CDN for fast global access — while the backend API handles the heavy lifting on the server.&lt;/p&gt;

&lt;p&gt;⚙️ &lt;strong&gt;Why I Moved Compression to the Backend&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Initially, I implemented compression on the frontend using WebAssembly builds of FFmpeg.&lt;br&gt;
It worked, but the performance wasn’t great — CPU utilization was low, and large files took ages to compress.&lt;/p&gt;

&lt;p&gt;By moving the compression process to the server side, I could fully leverage the machine’s CPU and I/O performance.&lt;br&gt;
This resulted in much faster compression, especially for longer videos or high-resolution formats (like 1080p or 4K).&lt;/p&gt;

&lt;p&gt;It also gave me more flexibility to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optimize compression presets dynamically&lt;/li&gt;
&lt;li&gt;Handle larger uploads&lt;/li&gt;
&lt;li&gt;Support multiple video formats (MP4, MOV, AVI, etc.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🧪 &lt;strong&gt;Lessons Learned&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontend compression sounds cool, but for heavy workloads, it’s just not efficient.&lt;/li&gt;
&lt;li&gt;FFmpeg remains unbeatable when it comes to quality-to-size ratio.&lt;/li&gt;
&lt;li&gt;Keeping the UI minimal is key — users just want results, not settings overload.&lt;/li&gt;
&lt;li&gt;Building static with Nuxt + FastAPI backend is a great combo for projects like this — quick to build, easy to deploy, and highly scalable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🌍 Try It Out&lt;/p&gt;

&lt;p&gt;If you’d like to give it a try, you can check it out here:&lt;br&gt;
👉 &lt;a href="https://videocompress.ai" rel="noopener noreferrer"&gt;https://videocompress.ai&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s completely free, no watermarks, no hidden limits.&lt;br&gt;
Would love to hear your feedback or suggestions for improvement!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>beginners</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
