<?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: Karishma</title>
    <description>The latest articles on DEV Community by Karishma (@karishma_dev).</description>
    <link>https://dev.to/karishma_dev</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%2F4016990%2F517ec3f3-adbf-45e5-9ca3-9bd2729e169c.png</url>
      <title>DEV Community: Karishma</title>
      <link>https://dev.to/karishma_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/karishma_dev"/>
    <language>en</language>
    <item>
      <title>Why I stopped using online image compressors and built a CLI instead</title>
      <dc:creator>Karishma</dc:creator>
      <pubDate>Mon, 06 Jul 2026 18:18:18 +0000</pubDate>
      <link>https://dev.to/karishma_dev/why-i-stopped-using-online-image-compressors-and-built-a-cli-instead-io6</link>
      <guid>https://dev.to/karishma_dev/why-i-stopped-using-online-image-compressors-and-built-a-cli-instead-io6</guid>
      <description>&lt;p&gt;Four years of optimizing React and Next.js projects taught me one thing: unoptimized images are everywhere, and nobody wants to fix them.&lt;/p&gt;

&lt;p&gt;Every project has the same pattern. Heavy PNG and JPG files are sitting inside &lt;code&gt;/public&lt;/code&gt;, there is no consistent image pipeline, and some of those files have no business being that large in a production codebase.&lt;/p&gt;

&lt;p&gt;This is especially common in small and mid-sized projects. There is no CDN transformation layer or dedicated asset pipeline. Images get added while the product is moving quickly, and the cleanup becomes a task for “later.”&lt;/p&gt;

&lt;p&gt;Later, of course, never comes.&lt;/p&gt;

&lt;p&gt;Then, at 1am, while refactoring an extremely vibe-coded Next.js project, I found myself doing the cleanup manually again.&lt;/p&gt;

&lt;p&gt;Find an image. Upload it to an online compressor. Hit the free limit. Open another tool. Convert a few more. Download everything. Replace the original files. Hunt through the codebase for every import and &lt;code&gt;src&lt;/code&gt; path. Hope I did not miss one.&lt;/p&gt;

&lt;p&gt;And I finally thought: I am a developer. Why am I doing this by hand?&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://www.npmjs.com/package/pixcrush" rel="noopener noreferrer"&gt;pixcrush&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx pixcrush &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One command to convert the images, compress them, and update their matching code references automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  “But doesn’t Next.js already optimize images?”
&lt;/h2&gt;

&lt;p&gt;Yes, and if your application uses &lt;code&gt;next/image&lt;/code&gt; consistently, you should absolutely take advantage of it.&lt;/p&gt;

&lt;p&gt;The Next.js &lt;code&gt;&amp;lt;Image&amp;gt;&lt;/code&gt; component can resize images for different devices, lazy-load them, and serve modern formats such as WebP. Files inside &lt;code&gt;/public&lt;/code&gt; can be referenced from the root URL, while statically imported images also give Next.js access to their intrinsic dimensions. The &lt;a href="https://nextjs.org/docs/app/getting-started/images" rel="noopener noreferrer"&gt;official Next.js image documentation&lt;/a&gt; explains these runtime optimizations in detail.&lt;/p&gt;

&lt;p&gt;But that solves a different layer of the problem.&lt;/p&gt;

&lt;p&gt;I wanted to clean up the source assets themselves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Replace heavy PNG and JPG files with smaller WebP files when conversion is worthwhile.&lt;/li&gt;
&lt;li&gt;Update existing imports and string-based image paths across the repository.&lt;/li&gt;
&lt;li&gt;Identify images that are no longer referenced.&lt;/li&gt;
&lt;li&gt;Make the migration reviewable before touching any files.&lt;/li&gt;
&lt;li&gt;Keep the process useful for projects that do not consistently use &lt;code&gt;next/image&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was not about replacing the Next.js image pipeline. It was about making a tedious codebase migration safe and repeatable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a simple format conversion becomes a code migration
&lt;/h2&gt;

&lt;p&gt;Consider a small project with images referenced in a few different ways:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;hero&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./assets/hero.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HomePage&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="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hero&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/images/team.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Our team"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;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;Converting &lt;code&gt;hero.png&lt;/code&gt; and &lt;code&gt;team.jpg&lt;/code&gt; is only half of the job. The code now points to files that no longer represent the desired output.&lt;/p&gt;

&lt;p&gt;After the migration, the references need to become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;hero&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./assets/hero.webp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HomePage&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="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;hero&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/images/team.webp"&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Our team"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;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;Now multiply that across &lt;code&gt;.js&lt;/code&gt;, &lt;code&gt;.jsx&lt;/code&gt;, &lt;code&gt;.ts&lt;/code&gt;, &lt;code&gt;.tsx&lt;/code&gt;, &lt;code&gt;.html&lt;/code&gt;, and &lt;code&gt;.json&lt;/code&gt; files, plus nested apps inside a Turborepo.&lt;/p&gt;

&lt;p&gt;That is no longer an image-conversion task. It is a codemod with image processing attached.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow I wanted
&lt;/h2&gt;

&lt;p&gt;I wanted the tool to perform five clear phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scan the repository for supported images and source files.&lt;/li&gt;
&lt;li&gt;Determine which images are actually referenced.&lt;/li&gt;
&lt;li&gt;Convert used PNG and JPG files to WebP.&lt;/li&gt;
&lt;li&gt;Update only the references connected to successful conversions.&lt;/li&gt;
&lt;li&gt;Report what changed, what was skipped, and what needs human review.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The default command is intentionally small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx pixcrush &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a first pass on an unfamiliar project, I prefer starting with a dry run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx pixcrush &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This previews the migration without writing files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Find the images and their references
&lt;/h2&gt;

&lt;p&gt;pixcrush scans for PNG, JPG, and JPEG files while ignoring directories such as &lt;code&gt;node_modules&lt;/code&gt;, &lt;code&gt;.next&lt;/code&gt;, &lt;code&gt;dist&lt;/code&gt;, &lt;code&gt;build&lt;/code&gt;, and &lt;code&gt;.git&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It separately collects the source files that may contain references:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.js  .jsx  .ts  .tsx  .html  .htm  .json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finding image files is easy. Determining whether they are used is the interesting part.&lt;/p&gt;

&lt;p&gt;For JavaScript and TypeScript, pixcrush parses the source with Babel using the JSX and TypeScript plugins. Babel’s parser produces an abstract syntax tree, which makes it possible to inspect actual string literals instead of blindly replacing every &lt;code&gt;.png&lt;/code&gt; substring. Babel documents its JSX and TypeScript parsing support in the &lt;a href="https://babeljs.io/docs/babel-parser/" rel="noopener noreferrer"&gt;&lt;code&gt;@babel/parser&lt;/code&gt; reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The tracker resolves several common patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;logo&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./logo.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/images/hero.jpg"&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imagePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/assets/product.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also handles image-bearing values in HTML and JSON, including &lt;code&gt;srcset&lt;/code&gt;-style strings and query parameters.&lt;/p&gt;

&lt;p&gt;For absolute paths such as &lt;code&gt;/images/hero.jpg&lt;/code&gt;, pixcrush reconciles the reference with matching &lt;code&gt;public&lt;/code&gt; directories. This matters in monorepos, where the real file may be located at something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/web/public/images/hero.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Images without a resolved reference are reported as unused instead of being converted automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Convert in memory, and keep the original when WebP is larger
&lt;/h2&gt;

&lt;p&gt;The actual image conversion uses &lt;a href="https://sharp.pixelplumbing.com/" rel="noopener noreferrer"&gt;Sharp&lt;/a&gt;, which supports WebP output and exposes a configurable quality setting.&lt;/p&gt;

&lt;p&gt;The important part is not just calling &lt;code&gt;.webp()&lt;/code&gt;. pixcrush creates the WebP data in memory first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;webpBuffer&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;sharp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imagePath&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="nx"&gt;quality&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBuffer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sharp’s &lt;a href="https://sharp.pixelplumbing.com/api-output/" rel="noopener noreferrer"&gt;&lt;code&gt;toBuffer()&lt;/code&gt; documentation&lt;/a&gt; confirms that WebP output can be generated directly as a buffer. That lets pixcrush compare the proposed output with the original before writing anything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webpBuffer&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="nx"&gt;originalSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Skip this conversion.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WebP is often smaller, but “modern format” does not automatically mean “smaller file” for every source image. If the generated WebP would be the same size or larger, pixcrush skips it.&lt;/p&gt;

&lt;p&gt;More importantly, skipped images are not added to the rewrite map. Their code references remain unchanged.&lt;/p&gt;

&lt;p&gt;The default WebP quality is &lt;code&gt;80&lt;/code&gt;, and it can be adjusted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx pixcrush &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--quality&lt;/span&gt; 90
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: Rewrite code without reformatting the entire file
&lt;/h2&gt;

&lt;p&gt;The riskiest part of the migration is not creating a &lt;code&gt;.webp&lt;/code&gt; file. It is changing the code safely.&lt;/p&gt;

&lt;p&gt;A global find-and-replace is tempting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.png → .webp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is also far too broad. It can modify unrelated text, references to images that were skipped, or paths that do not resolve to a file pixcrush converted.&lt;/p&gt;

&lt;p&gt;Instead, pixcrush builds a map containing only successful conversions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/absolute/path/hero.png → /absolute/path/hero.webp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It parses each JavaScript or TypeScript source file, visits its string literals, resolves candidate image paths, and creates targeted text replacements only for matches in that conversion map.&lt;/p&gt;

&lt;p&gt;The replacements are applied from the end of the file toward the beginning. This prevents an earlier edit from shifting the offsets required by a later edit.&lt;/p&gt;

&lt;p&gt;There is another subtle benefit: pixcrush does not regenerate the entire source file from the AST. It replaces only the extension inside the matched string. Existing formatting, comments, quote styles, and surrounding code remain untouched.&lt;/p&gt;

&lt;p&gt;Query strings are preserved too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/images/hero.png?v=2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/images/hero.webp?v=2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The safety rules matter more than the happy path
&lt;/h2&gt;

&lt;p&gt;Code-modifying tools should be conservative. I would rather have pixcrush warn about one path than confidently break a project.&lt;/p&gt;

&lt;p&gt;These are the guardrails I added:&lt;/p&gt;

&lt;h3&gt;
  
  
  Dry runs do not write files
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx pixcrush &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This runs the analysis, conversion calculation, and reference-matching flow without writing the WebP files or modified source.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamic paths are reported, not guessed
&lt;/h3&gt;

&lt;p&gt;pixcrush can understand a static value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/products/shoe.png"&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It cannot safely resolve every runtime value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`/products/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.png`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dynamic template paths are warned about so they can be reviewed manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Parse failures block destructive cleanup
&lt;/h3&gt;

&lt;p&gt;If a source file cannot be parsed, pixcrush records the failure. When &lt;code&gt;--delete-originals&lt;/code&gt; is enabled, original and unused images are deleted only if both the analysis and rewrite phases completed without parse failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Framework metadata images are excluded
&lt;/h3&gt;

&lt;p&gt;Files such as favicons, Apple touch icons, Open Graph images, Twitter images, and common PWA manifest icons may be discovered through framework conventions rather than ordinary source references.&lt;/p&gt;

&lt;p&gt;pixcrush excludes these patterns from the migration instead of incorrectly treating them as orphans.&lt;/p&gt;

&lt;h3&gt;
  
  
  External and inline images are left alone
&lt;/h3&gt;

&lt;p&gt;HTTP URLs and &lt;code&gt;data:&lt;/code&gt; values are not local assets, so the tracker ignores them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running the full migration
&lt;/h2&gt;

&lt;p&gt;My preferred workflow is:&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="c"&gt;# 1. Preview the migration&lt;/span&gt;
npx pixcrush &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;

&lt;span class="c"&gt;# 2. Review the reported conversions, warnings, and unused files&lt;/span&gt;

&lt;span class="c"&gt;# 3. Run the migration&lt;/span&gt;
npx pixcrush &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# 4. Run the application and its tests&lt;/span&gt;

&lt;span class="c"&gt;# 5. Remove converted originals only when ready&lt;/span&gt;
npx pixcrush &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--delete-originals&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final summary includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Images converted&lt;/li&gt;
&lt;li&gt;Space saved&lt;/li&gt;
&lt;li&gt;Source files updated&lt;/li&gt;
&lt;li&gt;Unused images detected&lt;/li&gt;
&lt;li&gt;Dynamic references or parsing issues requiring review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I still recommend running this on a clean Git branch. A dry run reduces surprises; Git gives you the final, exact review surface.&lt;/p&gt;

&lt;h2&gt;
  
  
  What pixcrush does not handle yet
&lt;/h2&gt;

&lt;p&gt;The current limitations are intentional and worth stating clearly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSS files are not parsed, so references inside &lt;code&gt;url(...)&lt;/code&gt; are not rewritten. An image referenced only from CSS may also appear in the unused-image report; review that list carefully before using &lt;code&gt;--delete-originals&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Dynamic paths are detected but not automatically converted.&lt;/li&gt;
&lt;li&gt;The image inputs are currently PNG, JPG, and JPEG.&lt;/li&gt;
&lt;li&gt;Framework-convention metadata images are excluded for safety.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a project relies heavily on CSS backgrounds or dynamically generated filenames, expect some manual work after the automated pass.&lt;/p&gt;

&lt;p&gt;“Automated” should not mean “pretend every edge case is safe.”&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed for me
&lt;/h2&gt;

&lt;p&gt;The biggest improvement is not that Sharp can produce a WebP file. The biggest improvement is that the full cleanup is now one reviewable operation.&lt;/p&gt;

&lt;p&gt;The old workflow looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;find → upload → hit a limit → try another tool → download
→ replace files → search the codebase → update paths → repeat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new workflow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx pixcrush &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
npx pixcrush &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the kind of automation I like: not a new platform, dashboard, subscription, or upload queue. Just a local tool that removes a chore and then gets out of the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;pixcrush is open source and runs locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx pixcrush &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/pixcrush" rel="noopener noreferrer"&gt;npm package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/karishma-dev/pixcrush" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try it on a real React, Next.js, or Turborepo project, I would especially love feedback on path-resolution edge cases and file types worth supporting next.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>react</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
