<?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: 王康</title>
    <description>The latest articles on DEV Community by 王康 (@_cafda18cb1684fc076f294).</description>
    <link>https://dev.to/_cafda18cb1684fc076f294</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%2F4181811%2F8d448d94-6696-484f-a225-67e4c70efb7e.jpg</url>
      <title>DEV Community: 王康</title>
      <link>https://dev.to/_cafda18cb1684fc076f294</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/_cafda18cb1684fc076f294"/>
    <language>en</language>
    <item>
      <title>Build an Image Compress Budget Gate Before Heavy Assets Reach Production</title>
      <dc:creator>王康</dc:creator>
      <pubDate>Tue, 20 Oct 2026 08:46:56 +0000</pubDate>
      <link>https://dev.to/_cafda18cb1684fc076f294/build-an-image-compress-budget-gate-before-heavy-assets-reach-production-49oc</link>
      <guid>https://dev.to/_cafda18cb1684fc076f294/build-an-image-compress-budget-gate-before-heavy-assets-reach-production-49oc</guid>
      <description>&lt;p&gt;The page looked fine in review. The deployment passed. Then someone opened it on a slower mobile connection and found that the new hero image was nearly 5 MB.&lt;/p&gt;

&lt;p&gt;Nothing was technically broken. The file existed, the browser could decode it, and the layout rendered correctly. The problem was that our definition of “ready” did not include an image size limit.&lt;/p&gt;

&lt;p&gt;That is where an image budget helps. Instead of treating image compress work as a final manual cleanup, we can make file size part of the build contract. If an asset exceeds the agreed limit, CI should explain the problem before the change reaches production.&lt;/p&gt;

&lt;p&gt;This tutorial builds a small, dependency-free image size gate with Node.js and runs it in GitHub Actions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define limits by asset role, not one universal number.&lt;/li&gt;
&lt;li&gt;Check individual files and the total image payload.&lt;/li&gt;
&lt;li&gt;Fail CI with an actionable message.&lt;/li&gt;
&lt;li&gt;Keep compression separate from validation.&lt;/li&gt;
&lt;li&gt;Adjust budgets using real production measurements.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why a valid image can still be a broken artifact
&lt;/h2&gt;

&lt;p&gt;Most build pipelines answer questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the file exist?&lt;/li&gt;
&lt;li&gt;Does the application compile?&lt;/li&gt;
&lt;li&gt;Do tests pass?&lt;/li&gt;
&lt;li&gt;Can the browser render the asset?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those checks are necessary, but they do not tell us whether a file is appropriate for delivery. A 4,000-pixel photograph may be valid while still being wasteful inside a 900-pixel content column.&lt;/p&gt;

&lt;p&gt;An image compress workflow reduces the file after someone notices the problem. An image budget moves the decision earlier. It turns “this feels too large” into a rule that can be reviewed, versioned, and enforced.&lt;/p&gt;

&lt;p&gt;The goal is not to make every image as small as possible. The goal is to catch accidental weight before it becomes a user-facing regression.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with budgets you can explain
&lt;/h2&gt;

&lt;p&gt;A single limit for every image is tempting, but it ignores how assets are used. A full-width hero, a product thumbnail, and a small icon have different requirements.&lt;/p&gt;

&lt;p&gt;Here is a reasonable starting point for a content site:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Asset group&lt;/th&gt;
&lt;th&gt;Example limit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hero image&lt;/td&gt;
&lt;td&gt;350 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Article image&lt;/td&gt;
&lt;td&gt;220 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thumbnail&lt;/td&gt;
&lt;td&gt;100 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Icon or badge&lt;/td&gt;
&lt;td&gt;30 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total images on one page&lt;/td&gt;
&lt;td&gt;1 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are not universal standards. A photography portfolio may accept larger files, while a documentation site can be stricter. What matters is that each limit reflects the rendered size, visual importance, and network conditions of the audience.&lt;/p&gt;

&lt;p&gt;Treat the image compress budget as a project constraint rather than a universal score. It should describe what your pages can afford, not force unrelated assets into the same preset.&lt;/p&gt;

&lt;p&gt;Start with current production data. Identify the heaviest pages, inspect their image requests, and choose a first budget that catches obvious mistakes without blocking normal work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a dependency-free size checker
&lt;/h2&gt;

&lt;p&gt;The following script scans an image directory, checks every supported file, and fails when either an individual file or the combined folder size exceeds the budget.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;scripts/check-image-budget.mjs&lt;/code&gt;:&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;readdir&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;extname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;relative&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:path&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;root&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;public/images&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;maxFileBytes&lt;/span&gt; &lt;span class="o"&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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAX_IMAGE_KB&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;)&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxTotalBytes&lt;/span&gt; &lt;span class="o"&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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAX_IMAGE_TOTAL_KB&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;)&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imageExtensions&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;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.jpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.webp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.avif&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.gif&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;]);&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;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;directory&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;entries&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;readdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;withFileTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;files&lt;/span&gt; &lt;span class="o"&gt;=&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;entry&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;entries&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;fullPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&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;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullPath&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageExtensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;extname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullPath&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;span class="k"&gt;return&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;function&lt;/span&gt; &lt;span class="nf"&gt;toKB&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="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;bytes&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; KB`&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;files&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;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&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;failures&lt;/span&gt; &lt;span class="o"&gt;=&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;totalBytes&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="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="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="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;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;totalBytes&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="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;gt;&lt;/span&gt; &lt;span class="nx"&gt;maxFileBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&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="s2"&gt; is &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;toKB&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="s2"&gt;; limit is &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;toKB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxFileBytes&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&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;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;totalBytes&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;maxTotalBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`Total image size is &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;toKB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;totalBytes&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;; limit is &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;toKB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxTotalBytes&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&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;failures&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;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;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;Image budget exceeded:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&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;failure&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;failures&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="s2"&gt;`- &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Resize, convert, or compress the affected assets, then run the check again.&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;1&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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`Image budget passed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; files, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nf"&gt;toKB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;totalBytes&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; total.`&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it locally with:&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="nv"&gt;MAX_IMAGE_KB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;250 &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;MAX_IMAGE_TOTAL_KB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1500 &lt;span class="se"&gt;\&lt;/span&gt;
node scripts/check-image-budget.mjs public/images
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This first version intentionally checks bytes, not perceived quality. CI can tell us that a file is too large; a person still needs to decide whether resizing, format conversion, or a gentler image compress setting is the correct fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Add the gate to GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Run the same command on pull requests so the feedback appears before merge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Image budget&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;public/images/**"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scripts/check-image-budget.mjs"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.github/workflows/image-budget.yml"&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;check-images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check image budget&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;MAX_IMAGE_KB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;250&lt;/span&gt;
          &lt;span class="na"&gt;MAX_IMAGE_TOTAL_KB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1500&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node scripts/check-image-budget.mjs public/images&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;paths&lt;/code&gt; filter avoids running the job when a pull request changes unrelated code. The limits remain visible in the workflow, which makes a budget change an explicit code review decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make failures actionable
&lt;/h2&gt;

&lt;p&gt;A gate is useful only when developers know how to recover. “Build failed” is not enough. The check should name the file, show its current size, state the allowed size, and suggest the next step.&lt;/p&gt;

&lt;p&gt;A useful image compress gate therefore reports a repair path, not only a red status icon.&lt;/p&gt;

&lt;p&gt;When a file fails, use this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check dimensions.&lt;/strong&gt; If the source is much wider than its largest rendered size, resize it first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the format.&lt;/strong&gt; A photograph exported as PNG may be better suited to JPEG, WebP, or AVIF.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply image compression.&lt;/strong&gt; Compare at least two quality levels at the actual display size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retest the page.&lt;/strong&gt; Confirm that text, gradients, transparency, and important details still look correct.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the budget check again.&lt;/strong&gt; Validation should use the final file that will be deployed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For occasional assets, a browser tool such as &lt;a href="https://imagecompress.ai/" rel="noopener noreferrer"&gt;ImageCompress&lt;/a&gt; can help compare a smaller output without opening a full image editor. For private or unreleased material, review the service’s file-handling policy or keep processing inside an approved local workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not let CI silently rewrite assets
&lt;/h2&gt;

&lt;p&gt;It may seem convenient to have CI automatically run an image compress command and commit the result. In practice, that hides an important visual decision.&lt;/p&gt;

&lt;p&gt;Different assets fail in different ways. Photographs can develop blocking around edges, screenshots can lose text clarity, and transparent graphics can gain halos. A fully automatic rewrite may pass the byte limit while damaging the image’s purpose.&lt;/p&gt;

&lt;p&gt;A safer separation is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;local tooling or a controlled build step creates optimized candidates;&lt;/li&gt;
&lt;li&gt;a developer reviews the visual result;&lt;/li&gt;
&lt;li&gt;CI validates the committed output against the budget.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Automation should prevent regressions, not remove the quality review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Improve the rule as the project grows
&lt;/h2&gt;

&lt;p&gt;The basic script is intentionally small. A production system can extend it in several ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use different limits for &lt;code&gt;hero/&lt;/code&gt;, &lt;code&gt;content/&lt;/code&gt;, and &lt;code&gt;thumbnails/&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;inspect pixel dimensions with an image metadata library;&lt;/li&gt;
&lt;li&gt;compare only files changed in the pull request;&lt;/li&gt;
&lt;li&gt;publish a size report as a pull request comment;&lt;/li&gt;
&lt;li&gt;track page-level transfer size with Lighthouse or a browser test;&lt;/li&gt;
&lt;li&gt;allow documented exceptions for assets that genuinely need more detail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Be careful with permanent exceptions. If every failure becomes a larger limit, the budget stops protecting anything. An exception should explain why the heavier asset is necessary and which page owns the cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this check does not solve
&lt;/h2&gt;

&lt;p&gt;A small file can still load late. The browser may discover it only after JavaScript runs, the server may omit effective caching, or the page may lazy-load its main visual by mistake.&lt;/p&gt;

&lt;p&gt;The budget covers transfer weight. It does not replace responsive &lt;code&gt;srcset&lt;/code&gt; candidates, correct &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt;, caching, request prioritization, or performance measurement in real browsers.&lt;/p&gt;

&lt;p&gt;Think of it as one reliable boundary in a larger delivery system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-merge checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] The source dimensions match the asset’s intended display size.&lt;/li&gt;
&lt;li&gt;[ ] The format fits the visual content.&lt;/li&gt;
&lt;li&gt;[ ] The &lt;strong&gt;image compress&lt;/strong&gt; result was reviewed at normal zoom.&lt;/li&gt;
&lt;li&gt;[ ] The file passes its individual size budget.&lt;/li&gt;
&lt;li&gt;[ ] The page stays within its total image budget.&lt;/li&gt;
&lt;li&gt;[ ] Responsive variants exist where they provide a real benefit.&lt;/li&gt;
&lt;li&gt;[ ] The pull request explains any approved exception.&lt;/li&gt;
&lt;li&gt;[ ] The deployed request size is verified in browser tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;The most expensive image regressions are often ordinary mistakes: an original camera export, a screenshot saved in the wrong format, or a replacement asset with much larger dimensions than the file before it.&lt;/p&gt;

&lt;p&gt;A small CI gate catches those mistakes while they are still easy to fix. It also turns image compress decisions into shared engineering rules instead of relying on someone to remember a checklist before every release.&lt;/p&gt;

&lt;p&gt;Start with one directory and one understandable limit. Run the check in warning mode if necessary, observe what it catches, and tighten the budget using real page data. Once the rule reflects the project’s needs, make it a required check.&lt;/p&gt;

&lt;p&gt;The build should not only prove that an image can ship. It should also prove that the image belongs in the delivery budget.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Image Compress Workflow: Reduce File Size Without Ruining Quality</title>
      <dc:creator>王康</dc:creator>
      <pubDate>Tue, 20 Oct 2026 03:10:20 +0000</pubDate>
      <link>https://dev.to/_cafda18cb1684fc076f294/image-compress-workflow-reduce-file-size-without-ruining-quality-jbf</link>
      <guid>https://dev.to/_cafda18cb1684fc076f294/image-compress-workflow-reduce-file-size-without-ruining-quality-jbf</guid>
      <description>&lt;p&gt;Large images are one of the easiest ways to make a fast website feel slow. A single photo exported directly from a camera or design tool can outweigh the HTML, CSS, and JavaScript used to render the entire page.&lt;/p&gt;

&lt;p&gt;An effective image compress workflow is not about pushing every file toward the lowest possible number. It is about removing data the visitor will never see while preserving the detail that makes the image useful.&lt;/p&gt;

&lt;p&gt;This guide walks through a repeatable process you can apply to blogs, product pages, portfolios, and documentation sites.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Resize before compressing.&lt;/li&gt;
&lt;li&gt;Match the file format to the image content.&lt;/li&gt;
&lt;li&gt;Judge quality at the actual display size.&lt;/li&gt;
&lt;li&gt;Deliver responsive variants and lazy-load only non-critical images.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why image weight matters
&lt;/h2&gt;

&lt;p&gt;Images often account for a large share of a page's transferred bytes. Reducing that weight can shorten downloads, lower mobile data usage, and make content appear sooner on slower connections.&lt;/p&gt;

&lt;p&gt;However, image compress work is only one part of web performance. A small hero can still appear late if its URL is hidden behind client-side rendering, while an image discovered immediately can still delay the page if it is several megabytes. Measure transfer size, discovery time, and rendering separately.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Resize before you compress
&lt;/h2&gt;

&lt;p&gt;The most common mistake is placing a 5,000-pixel photo inside a content column that never exceeds 900 pixels. CSS may display it at the correct width, but the browser still downloads the original file.&lt;/p&gt;

&lt;p&gt;A practical image compress process begins with the rendered dimensions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Find the largest rendered size.&lt;/li&gt;
&lt;li&gt;Resize the source while preserving its aspect ratio.&lt;/li&gt;
&lt;li&gt;Compress the resized version.&lt;/li&gt;
&lt;li&gt;Preview it in the real layout.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If an article image displays at 800 pixels wide, a 1,200- or 1,600-pixel source may be enough. Resizing first usually reaches a reasonable file size without an aggressive quality setting.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Choose a format based on the content
&lt;/h2&gt;

&lt;p&gt;No format is best for every asset. Choose based on whether the image is a photograph, screenshot, logo, or transparent graphic.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Good for&lt;/th&gt;
&lt;th&gt;Watch out for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JPEG&lt;/td&gt;
&lt;td&gt;Photos, gradients, product shots&lt;/td&gt;
&lt;td&gt;No transparency; artifacts appear at low quality&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PNG&lt;/td&gt;
&lt;td&gt;Logos, UI screenshots, sharp lines&lt;/td&gt;
&lt;td&gt;Photographs can become unnecessarily large&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebP&lt;/td&gt;
&lt;td&gt;Photos and graphics for modern web delivery&lt;/td&gt;
&lt;td&gt;Keep your build and fallback strategy consistent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AVIF&lt;/td&gt;
&lt;td&gt;High compression efficiency for supported workflows&lt;/td&gt;
&lt;td&gt;Encoding can be slower; verify tool and browser requirements&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Format selection is part of the image compress decision. Converting a photo from PNG to WebP may save more than repeatedly lowering PNG quality, while a text-heavy screenshot may look better as PNG or lossless WebP.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Evaluate quality where users will see it
&lt;/h2&gt;

&lt;p&gt;A quality value of &lt;code&gt;80&lt;/code&gt; does not produce identical results for every image. Hair, foliage, gradients, and small interface text often reveal damage early.&lt;/p&gt;

&lt;p&gt;When reviewing an image compress result, inspect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;faces and product edges;&lt;/li&gt;
&lt;li&gt;text and icons in screenshots;&lt;/li&gt;
&lt;li&gt;smooth gradients and transparent edges;&lt;/li&gt;
&lt;li&gt;QR codes that must remain readable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, view it at the real rendered size. A tiny artifact at 400% may not matter, while blurred text can remain distracting on a phone. File size is a constraint, not the only success metric.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Send responsive image candidates
&lt;/h2&gt;

&lt;p&gt;Even a well-compressed desktop image may be wasteful on a small screen. Use &lt;code&gt;srcset&lt;/code&gt; and &lt;code&gt;sizes&lt;/code&gt; so the browser can choose a suitable candidate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt;
  &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"workspace-960.webp"&lt;/span&gt;
  &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"workspace-480.webp 480w,
          workspace-960.webp 960w,
          workspace-1440.webp 1440w"&lt;/span&gt;
  &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"(max-width: 768px) 100vw, 960px"&lt;/span&gt;
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"960"&lt;/span&gt;
  &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"640"&lt;/span&gt;
  &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"A desk with a laptop and design sketches"&lt;/span&gt;
&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each candidate needs its own image compress pass. The browser selects among the files you provide; it does not automatically optimize an oversized source.&lt;/p&gt;

&lt;p&gt;Including &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; gives the browser an aspect ratio before the file arrives, reserving space and reducing layout movement.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Use lazy loading selectively
&lt;/h2&gt;

&lt;p&gt;Below-the-fold images are good candidates for &lt;code&gt;loading="lazy"&lt;/code&gt;. Do not apply it to every image by habit. The main hero or likely Largest Contentful Paint image usually needs to be fetched early, and delaying it can make the page feel slower even after image compress optimization.&lt;/p&gt;

&lt;p&gt;Compression changes transfer size. Lazy loading changes request timing. Use each technique for the problem it actually solves.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Build a repeatable optimization routine
&lt;/h2&gt;

&lt;p&gt;For occasional blog images, a manual workflow can be enough:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Save the untouched original.&lt;/li&gt;
&lt;li&gt;Crop unused space and generate the required dimensions.&lt;/li&gt;
&lt;li&gt;Select the output format.&lt;/li&gt;
&lt;li&gt;Apply image compress settings and compare the result.&lt;/li&gt;
&lt;li&gt;Test on desktop, mobile, and a slower network profile.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a small batch, a browser-based tool can make comparison convenient. For example, &lt;a href="https://imagecompress.ai/" rel="noopener noreferrer"&gt;ImageCompress&lt;/a&gt; can be used when you want to prepare common web images without opening a full editing application.&lt;/p&gt;

&lt;p&gt;Before using any online service, check how files are processed and deleted. Confidential documents, internal designs, and unreleased product images should stay in a trusted environment.&lt;/p&gt;

&lt;p&gt;Once optimization becomes routine, automate maximum dimensions, formats, quality ranges, and responsive variants in the build system or CDN. Verify transferred files in the browser Network panel and compare performance tests under consistent conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compressing the full-resolution original:&lt;/strong&gt; resize to the required dimensions first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimizing only for a percentage:&lt;/strong&gt; a large reduction is meaningless if text becomes unreadable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Applying one preset to every asset:&lt;/strong&gt; photos, logos, and screenshots need different settings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ignoring mobile candidates:&lt;/strong&gt; a compressed desktop file can still be too large for a phone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lazy-loading the hero image:&lt;/strong&gt; prioritize critical visuals and defer only lower content.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pre-publish checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] The image dimensions match the largest rendered size.&lt;/li&gt;
&lt;li&gt;[ ] The format fits the image content.&lt;/li&gt;
&lt;li&gt;[ ] The image compress result has been checked at actual display size.&lt;/li&gt;
&lt;li&gt;[ ] Responsive candidates are available where they provide value.&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;width&lt;/code&gt;, &lt;code&gt;height&lt;/code&gt;, and meaningful &lt;code&gt;alt&lt;/code&gt; text are present.&lt;/li&gt;
&lt;li&gt;[ ] Below-the-fold images use lazy loading.&lt;/li&gt;
&lt;li&gt;[ ] The critical hero image is not unnecessarily delayed.&lt;/li&gt;
&lt;li&gt;[ ] The original asset is stored separately.&lt;/li&gt;
&lt;li&gt;[ ] The real network request was verified in browser tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;A good image compress workflow delivers only the pixels and bytes a user needs, at the moment they need them.&lt;/p&gt;

&lt;p&gt;Start with one high-impact image. Resize it, test two suitable formats, compare quality at the rendered size, and measure the actual transfer. Once the result is repeatable, turn the decisions into build rules.&lt;/p&gt;

&lt;p&gt;The result is better performance without sacrificing the visual quality your content depends on.&lt;/p&gt;

</description>
      <category>image</category>
      <category>compressor</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>I Built a Free Online PDF Compressor — No Signup, No Watermark</title>
      <dc:creator>王康</dc:creator>
      <pubDate>Tue, 13 Oct 2026 10:02:33 +0000</pubDate>
      <link>https://dev.to/_cafda18cb1684fc076f294/i-built-a-free-online-pdf-compressor-no-signup-no-watermark-28kh</link>
      <guid>https://dev.to/_cafda18cb1684fc076f294/i-built-a-free-online-pdf-compressor-no-signup-no-watermark-28kh</guid>
      <description>&lt;p&gt;Hi everyone!&lt;/p&gt;

&lt;p&gt;I recently built a small project called &lt;a href="https://pdfcompress.ai/" rel="noopener noreferrer"&gt;PDFCompress&lt;/a&gt; — a free online tool that helps people reduce PDF file sizes without installing software, creating an account, or adding a watermark.&lt;/p&gt;

&lt;p&gt;The goal was simple: make PDF compression fast, flexible, and easy enough for anyone to use.&lt;/p&gt;

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

&lt;p&gt;Large PDF files are surprisingly inconvenient.&lt;/p&gt;

&lt;p&gt;They can exceed email attachment limits, take too long to upload, consume unnecessary storage space, and become difficult to share on mobile devices or slower networks.&lt;/p&gt;

&lt;p&gt;There are already plenty of online PDF compressors, but many of them come with frustrating restrictions: mandatory registration, hidden fees, watermarks, small upload limits, or very little control over the final result.&lt;/p&gt;

&lt;p&gt;I wanted to create a simpler workflow:&lt;/p&gt;

&lt;p&gt;Upload → choose a compression level → compress → download&lt;/p&gt;

&lt;p&gt;No unnecessary steps and no confusing interface.&lt;/p&gt;

&lt;p&gt;🛠️ What I Built&lt;/p&gt;

&lt;p&gt;PDFCompress works directly in the browser and supports PDF files up to 1 GB. Users can upload and process up to five files at once, which is useful when compressing reports, scanned documents, presentations, application materials, or other large PDFs.&lt;/p&gt;

&lt;p&gt;The tool provides several compression options:&lt;/p&gt;

&lt;p&gt;Low compression: preserves more detail and is suitable for printing, portfolios, and professional documents.&lt;/p&gt;

&lt;p&gt;Medium compression: balances quality and file size for everyday sharing.&lt;/p&gt;

&lt;p&gt;High compression: produces the smallest possible file for email attachments and online uploads.&lt;/p&gt;

&lt;p&gt;Grayscale mode: removes unnecessary color information for additional size reduction.&lt;/p&gt;

&lt;p&gt;Depending on the document and selected mode, PDFCompress can typically reduce its size by approximately 20%–80% while keeping the text, images, and layout clear.&lt;/p&gt;

&lt;p&gt;⚙️ Balancing File Size and Quality&lt;/p&gt;

&lt;p&gt;One of the biggest challenges with PDF compression is that there is no single setting that works perfectly for every document.&lt;/p&gt;

&lt;p&gt;A scanned PDF containing hundreds of images needs a different strategy from a mostly text-based report. Compress too little, and the resulting file is still too large. Compress too aggressively, and images or small text may lose clarity.&lt;/p&gt;

&lt;p&gt;Instead of hiding this tradeoff, PDFCompress gives users a few straightforward compression levels. This keeps the interface simple while still allowing people to decide what matters most for their document: visual quality, smaller file size, or a balance between the two.&lt;/p&gt;

&lt;p&gt;🔒 Privacy Was Part of the Product Design&lt;/p&gt;

&lt;p&gt;PDF files often contain personal, academic, financial, or business information, so privacy could not be treated as an afterthought.&lt;/p&gt;

&lt;p&gt;Files uploaded to PDFCompress are encrypted during transfer and processing. Both the original and compressed files are automatically deleted within 24 hours and are not stored or shared.&lt;/p&gt;

&lt;p&gt;The service also requires no account, which means users can compress a document without providing personal registration details.&lt;/p&gt;

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

&lt;p&gt;A few things became especially clear while building the product:&lt;/p&gt;

&lt;p&gt;Users care about the final file size, but they care just as much about whether the document still looks good.&lt;/p&gt;

&lt;p&gt;A single compression preset cannot handle every type of PDF effectively.&lt;/p&gt;

&lt;p&gt;Supporting large files and batch processing makes the tool much more useful in real workflows.&lt;/p&gt;

&lt;p&gt;Privacy information should be visible before users upload a file.&lt;/p&gt;

&lt;p&gt;A focused interface is often better than a feature-heavy one—most people simply want to upload a PDF, make it smaller, and download it.&lt;/p&gt;

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

&lt;p&gt;If you regularly need to email reports, submit documents through online portals, or save storage space, you can try PDFCompress here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://pdfcompress.ai/" rel="noopener noreferrer"&gt;https://pdfcompress.ai/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is free to use, requires no registration, and adds no watermark to the compressed document.&lt;/p&gt;

&lt;p&gt;I would love to hear your feedback—esp&lt;/p&gt;

</description>
      <category>pdf</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>buildinpublic</category>
    </item>
  </channel>
</rss>
