<?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: Imazeo</title>
    <description>The latest articles on DEV Community by Imazeo (imazeo).</description>
    <link>https://dev.to/imazeo</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%2Forganization%2Fprofile_image%2F15957%2Fffea4173-4fc0-44bc-b9d0-cc4fe4af6ebf.png</url>
      <title>DEV Community: Imazeo</title>
      <link>https://dev.to/imazeo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/imazeo"/>
    <language>en</language>
    <item>
      <title>How to Optimize Images for the Web Without Guesswork</title>
      <dc:creator>MANISH BALODIA</dc:creator>
      <pubDate>Mon, 09 Nov 2026 04:49:34 +0000</pubDate>
      <link>https://dev.to/imazeo/how-to-optimize-images-for-the-web-without-guesswork-4nim</link>
      <guid>https://dev.to/imazeo/how-to-optimize-images-for-the-web-without-guesswork-4nim</guid>
      <description>&lt;p&gt;A common image performance problem is surprisingly simple: a browser downloads a 3000-pixel image, only to display it inside a 350-pixel-wide card.&lt;/p&gt;

&lt;p&gt;The image looks fine. The layout works. But the browser is transferring far more data than it needs.&lt;/p&gt;

&lt;p&gt;When optimizing images, I find it more useful to think about three things in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dimensions:&lt;/strong&gt; How large does the image actually need to be?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Format:&lt;/strong&gt; Which format makes sense for this type of content?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quality:&lt;/strong&gt; How much compression can you apply without affecting the result?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's walk through a small example using Node.js and Sharp.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start with dimensions
&lt;/h2&gt;

&lt;p&gt;Suppose you have a product image that's 3000 × 2000 pixels.&lt;/p&gt;

&lt;p&gt;Your website displays it in a card that's approximately 350 pixels wide.&lt;/p&gt;

&lt;p&gt;You probably don't need to serve the original file.&lt;/p&gt;

&lt;p&gt;Depending on the layout and device pixel density, generating 640px and 1200px versions could be a reasonable starting point.&lt;/p&gt;

&lt;p&gt;The exact sizes should come from your layout, not from a fixed rule.&lt;/p&gt;

&lt;p&gt;One important detail: CSS resizing does not reduce the file the browser downloads. You need appropriately sized image assets.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Choose the right format
&lt;/h2&gt;

&lt;p&gt;Different formats have different strengths.&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;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;JPEG&lt;/td&gt;
&lt;td&gt;Photographs and broad compatibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebP&lt;/td&gt;
&lt;td&gt;Most modern website images&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AVIF&lt;/td&gt;
&lt;td&gt;Potentially smaller image transfers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PNG&lt;/td&gt;
&lt;td&gt;Lossless graphics and pixel-precise assets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SVG&lt;/td&gt;
&lt;td&gt;Icons and vector illustrations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For product photography, I usually start by comparing JPEG and WebP.&lt;/p&gt;

&lt;p&gt;AVIF is also worth testing, but don't assume it always produces the smallest acceptable file. Results depend on the image and encoder settings.&lt;/p&gt;

&lt;p&gt;The goal isn't to use the newest format. It's to deliver an image that looks good without transferring unnecessary bytes.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Generate optimized images with Sharp
&lt;/h2&gt;

&lt;p&gt;Sharp is a popular image-processing library for Node.js.&lt;/p&gt;

&lt;p&gt;Install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;sharp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put a source image named &lt;code&gt;photo.jpg&lt;/code&gt; in your project directory. For this example, use a source at least 1200 pixels wide.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;optimize.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="nx"&gt;sharp&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;sharp&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;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;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;photo.jpg&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;widths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1200&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;width&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;widths&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;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`photo-&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="s2"&gt;.webp`&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;sharp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rotate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resize&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="na"&gt;withoutEnlargement&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="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;webp&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;78&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;output&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;output&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;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;output&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;size&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node optimize.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should now have two WebP images.&lt;/p&gt;

&lt;p&gt;Open both and compare them with the original.&lt;/p&gt;

&lt;p&gt;Pay attention to small text, edges, texture, and areas containing subtle color variations.&lt;/p&gt;

&lt;p&gt;A quality setting of 78 is just a starting point. It isn't a universal recommendation.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Serve the correct image
&lt;/h2&gt;

&lt;p&gt;Creating smaller files is only half the job.&lt;/p&gt;

&lt;p&gt;Your browser also needs to know which size to download.&lt;/p&gt;

&lt;p&gt;For example:&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;"/images/photo-1200.webp"&lt;/span&gt;
  &lt;span class="na"&gt;srcset=&lt;/span&gt;&lt;span class="s"&gt;"
    /images/photo-640.webp 640w,
    /images/photo-1200.webp 1200w
  "&lt;/span&gt;
  &lt;span class="na"&gt;sizes=&lt;/span&gt;&lt;span class="s"&gt;"(max-width: 720px) 100vw, 720px"&lt;/span&gt;
  &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;"1200"&lt;/span&gt;
  &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;"800"&lt;/span&gt;
  &lt;span class="na"&gt;alt=&lt;/span&gt;&lt;span class="s"&gt;"Product photograph"&lt;/span&gt;
  &lt;span class="na"&gt;loading=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&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;This example assumes a 3:2 source image and that the generated assets are available under &lt;code&gt;/images/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;srcset&lt;/code&gt; attribute provides image alternatives. The &lt;code&gt;sizes&lt;/code&gt; attribute tells the browser approximately how much space the image will occupy.&lt;/p&gt;

&lt;p&gt;The browser can then select an appropriate source.&lt;/p&gt;

&lt;p&gt;Also, don't automatically lazy-load your main hero image. Images visible immediately when the page loads may need to load promptly.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Measure the result
&lt;/h2&gt;

&lt;p&gt;After optimization, open Chrome DevTools.&lt;/p&gt;

&lt;p&gt;Check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which image file was requested?&lt;/li&gt;
&lt;li&gt;How many bytes were transferred?&lt;/li&gt;
&lt;li&gt;Did the mobile viewport select the expected source?&lt;/li&gt;
&lt;li&gt;Does the image still look good at its actual display size?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then check the page's performance.&lt;/p&gt;

&lt;p&gt;A smaller image may help, but optimizing one file doesn't automatically fix slow page loading. Server response times, JavaScript, CSS, and the position of your main image also matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. When should image processing move to an API?
&lt;/h2&gt;

&lt;p&gt;A small Sharp script works well when you control a limited set of images.&lt;/p&gt;

&lt;p&gt;Things become different when an application handles user uploads, product catalogs, multiple output formats, or dynamically requested dimensions.&lt;/p&gt;

&lt;p&gt;In those situations, a reusable image-processing API can keep transformation rules consistent across applications.&lt;/p&gt;

&lt;p&gt;This is one of the workflows we're working on with &lt;a href="https://imazeo.com/" rel="noopener noreferrer"&gt;Imazeo&lt;/a&gt;, alongside PDF processing and other media tools.&lt;/p&gt;

&lt;p&gt;Regardless of whether you use Sharp directly or an API, the same principle applies:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start with the actual display requirements, generate suitable assets, and measure what the browser receives.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;Before optimizing an entire image library, pick one real page and test a single image.&lt;/p&gt;

&lt;p&gt;Resize it, compare formats, inspect the quality, and check the browser's network requests.&lt;/p&gt;

&lt;p&gt;That small experiment will often tell you more than following a generic compression checklist.&lt;/p&gt;




&lt;p&gt;Originally published on the &lt;a href="https://blog.imazeo.com/how-to-optimize-images-for-the-web/" rel="noopener noreferrer"&gt;Imazeo Blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>node</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
