<?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: Muhaymin Bin Mehmood</title>
    <description>The latest articles on DEV Community by Muhaymin Bin Mehmood (@muhayminbinmehmood).</description>
    <link>https://dev.to/muhayminbinmehmood</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F862113%2F6e7ab150-0bd1-48ee-93a6-02227f19748c.webp</url>
      <title>DEV Community: Muhaymin Bin Mehmood</title>
      <link>https://dev.to/muhayminbinmehmood</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/muhayminbinmehmood"/>
    <language>en</language>
    <item>
      <title>I Don't Want to Build 100 Random Tools for SEO</title>
      <dc:creator>Muhaymin Bin Mehmood</dc:creator>
      <pubDate>Sun, 13 Sep 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/muhayminbinmehmood/i-dont-want-to-build-100-random-tools-for-seo-4i18</link>
      <guid>https://dev.to/muhayminbinmehmood/i-dont-want-to-build-100-random-tools-for-seo-4i18</guid>
      <description>&lt;p&gt;There is a tempting strategy for utility websites:&lt;/p&gt;

&lt;p&gt;Build a lot of tools.&lt;/p&gt;

&lt;p&gt;Then build more.&lt;/p&gt;

&lt;p&gt;Then keep going until the sitemap is enormous.&lt;/p&gt;

&lt;p&gt;You can easily imagine the backlog:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PNG converter
JPG converter
QR generator
word counter
percentage calculator
JSON formatter
random picker
password generator
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some of those pages may attract search traffic.&lt;/p&gt;

&lt;p&gt;But I do not think "more tools" automatically means "better product."&lt;/p&gt;

&lt;h2&gt;
  
  
  Traffic and product value are different
&lt;/h2&gt;

&lt;p&gt;A page can rank.&lt;/p&gt;

&lt;p&gt;A page can get clicks.&lt;/p&gt;

&lt;p&gt;A page can even generate ad revenue.&lt;/p&gt;

&lt;p&gt;None of that guarantees the user has a reason to return.&lt;/p&gt;

&lt;p&gt;A collection of unrelated utilities often creates isolated sessions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Google search
   ↓
Use tiny tool
   ↓
Leave
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That can be a valid business.&lt;/p&gt;

&lt;p&gt;It is just not the product I want to build.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question I now ask
&lt;/h2&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What other tool has search volume?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I prefer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What does the user need immediately before or after the current task?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That changes the roadmap.&lt;/p&gt;

&lt;p&gt;Imagine the user is preparing product images.&lt;/p&gt;

&lt;p&gt;Their workflow may be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Receive supplier photos
        ↓
Resize
        ↓
Convert format
        ↓
Compress
        ↓
Prepare marketplace variants
        ↓
Generate product barcodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those tools belong near each other.&lt;/p&gt;

&lt;p&gt;They share a user.&lt;/p&gt;

&lt;p&gt;They share context.&lt;/p&gt;

&lt;p&gt;They may eventually share data and automation.&lt;/p&gt;

&lt;p&gt;That is a product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Marketing has the same pattern
&lt;/h2&gt;

&lt;p&gt;A marketer may do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create campaign image
        ↓
Resize for channels
        ↓
Create landing URL
        ↓
Add tracking
        ↓
Shorten link
        ↓
Generate QR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, the tools are connected.&lt;/p&gt;

&lt;p&gt;A social resizer and a QR-code generator may look unrelated from a technical perspective.&lt;/p&gt;

&lt;p&gt;From the user's campaign workflow, they can be part of the same job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connected workflows create stronger expansion
&lt;/h2&gt;

&lt;p&gt;If tools are connected, you can eventually build experiences like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Upload one campaign asset
   ↓
Generate 8 platform versions
   ↓
Create tracked destination
   ↓
Generate QR
   ↓
Export campaign package
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the product is not a directory.&lt;/p&gt;

&lt;p&gt;It is a workflow engine.&lt;/p&gt;

&lt;p&gt;That is much more interesting to me.&lt;/p&gt;

&lt;h2&gt;
  
  
  SEO should support the product
&lt;/h2&gt;

&lt;p&gt;I still care about search.&lt;/p&gt;

&lt;p&gt;Utility products can benefit enormously from search intent because users often describe the exact job they need done.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;webp to jpg&lt;/li&gt;
&lt;li&gt;bulk image converter&lt;/li&gt;
&lt;li&gt;social media image resizer&lt;/li&gt;
&lt;li&gt;barcode generator&lt;/li&gt;
&lt;li&gt;qr code generator&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are useful entry points.&lt;/p&gt;

&lt;p&gt;But the content should lead into a coherent product experience.&lt;/p&gt;

&lt;p&gt;The goal is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search intent
   ↓
Useful free tool
   ↓
Related workflow
   ↓
Return usage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search intent
   ↓
Random tool
   ↓
Ad
   ↓
Goodbye
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  This is the direction behind BatchSet
&lt;/h2&gt;

&lt;p&gt;I am building &lt;a href="https://batchset.com/" rel="noopener noreferrer"&gt;BatchSet&lt;/a&gt; around image, e-commerce, and marketing workflows.&lt;/p&gt;

&lt;p&gt;The current toolkit includes areas such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;image conversion&lt;/li&gt;
&lt;li&gt;bulk conversion&lt;/li&gt;
&lt;li&gt;resizing&lt;/li&gt;
&lt;li&gt;social-media preparation&lt;/li&gt;
&lt;li&gt;QR generation&lt;/li&gt;
&lt;li&gt;URL shortening&lt;/li&gt;
&lt;li&gt;barcode generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can browse the current tool collection here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://batchset.com/tools" rel="noopener noreferrer"&gt;BatchSet Tools&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The goal is not to add every utility that exists on the internet.&lt;/p&gt;

&lt;p&gt;The goal is to make the workflows around these users increasingly connected.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple roadmap filter
&lt;/h2&gt;

&lt;p&gt;Before building a new tool, I now like asking:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Does the existing audience need it?
&lt;/h3&gt;

&lt;p&gt;If the current users are e-commerce sellers, marketers, developers, or content teams, does the new tool serve them?&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Does it connect to an existing workflow?
&lt;/h3&gt;

&lt;p&gt;Can the output of one tool become the input of another?&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Can it share infrastructure?
&lt;/h3&gt;

&lt;p&gt;Can the tool reuse:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;file pipeline&lt;/li&gt;
&lt;li&gt;account system&lt;/li&gt;
&lt;li&gt;analytics&lt;/li&gt;
&lt;li&gt;export logic&lt;/li&gt;
&lt;li&gt;presets&lt;/li&gt;
&lt;li&gt;batch processing&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Does it improve retention?
&lt;/h3&gt;

&lt;p&gt;Will existing users have another reason to return?&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Is search demand a bonus or the only reason?
&lt;/h3&gt;

&lt;p&gt;If the only argument is "keyword volume," I become more cautious.&lt;/p&gt;

&lt;h2&gt;
  
  
  The compounding effect
&lt;/h2&gt;

&lt;p&gt;Connected tools can compound.&lt;/p&gt;

&lt;p&gt;A user who arrives for conversion may discover bulk processing.&lt;/p&gt;

&lt;p&gt;A user who arrives for social resizing may need QR codes.&lt;/p&gt;

&lt;p&gt;A seller who arrives for image optimization may need barcodes.&lt;/p&gt;

&lt;p&gt;The platform becomes more valuable because the tools reinforce one another.&lt;/p&gt;

&lt;p&gt;That is different from simply increasing page count.&lt;/p&gt;

&lt;h2&gt;
  
  
  The risk
&lt;/h2&gt;

&lt;p&gt;There is also a danger in "all-in-one."&lt;/p&gt;

&lt;p&gt;Products can become bloated.&lt;/p&gt;

&lt;p&gt;Navigation becomes confusing.&lt;/p&gt;

&lt;p&gt;Features become shallow.&lt;/p&gt;

&lt;p&gt;The answer is not to connect everything.&lt;/p&gt;

&lt;p&gt;The answer is to connect workflows that genuinely belong together while keeping individual tools fast and understandable.&lt;/p&gt;

&lt;p&gt;That balance is hard.&lt;/p&gt;

&lt;p&gt;But it is a better problem than maintaining 500 unrelated utilities nobody remembers.&lt;/p&gt;

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

&lt;p&gt;SEO can tell you what people search for.&lt;/p&gt;

&lt;p&gt;It should not be the only thing deciding what your product becomes.&lt;/p&gt;

&lt;p&gt;For me, the stronger question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Can this new tool make an existing user's workflow meaningfully better?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the answer is yes, search traffic can become an acquisition channel for a real product.&lt;/p&gt;

&lt;p&gt;If the answer is no, I am probably just adding another page to the internet.&lt;/p&gt;

</description>
      <category>saas</category>
      <category>startup</category>
      <category>seo</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>I Built the Signup Wall First. Then I Realized It Was the Wrong Feature.</title>
      <dc:creator>Muhaymin Bin Mehmood</dc:creator>
      <pubDate>Sat, 12 Sep 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/muhayminbinmehmood/i-built-the-signup-wall-first-then-i-realized-it-was-the-wrong-feature-4j7n</link>
      <guid>https://dev.to/muhayminbinmehmood/i-built-the-signup-wall-first-then-i-realized-it-was-the-wrong-feature-4j7n</guid>
      <description>&lt;p&gt;There is a very normal SaaS flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Landing page
   ↓
Create account
   ↓
Verify email
   ↓
Enter app
   ↓
Try feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I built a version of that pattern too.&lt;/p&gt;

&lt;p&gt;Then I started questioning the most basic part of it.&lt;/p&gt;

&lt;p&gt;Why should somebody create an account &lt;strong&gt;before&lt;/strong&gt; they know whether the tool solves their problem?&lt;/p&gt;

&lt;p&gt;For some products, registration is essential.&lt;/p&gt;

&lt;p&gt;For others, it is just inherited behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  The product question I should have asked earlier
&lt;/h2&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How do I get more users to sign up?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I should have asked:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Why does this action require an account at all?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those are completely different questions.&lt;/p&gt;

&lt;p&gt;The first assumes the wall should exist.&lt;/p&gt;

&lt;p&gt;The second forces you to justify it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When registration makes sense
&lt;/h2&gt;

&lt;p&gt;Accounts are valuable when users need persistent state.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;saved projects&lt;/li&gt;
&lt;li&gt;billing&lt;/li&gt;
&lt;li&gt;team collaboration&lt;/li&gt;
&lt;li&gt;usage history&lt;/li&gt;
&lt;li&gt;API keys&lt;/li&gt;
&lt;li&gt;shared workspaces&lt;/li&gt;
&lt;li&gt;analytics dashboards&lt;/li&gt;
&lt;li&gt;cloud storage&lt;/li&gt;
&lt;li&gt;preferences synced across devices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In those cases, identity is part of the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  When registration can be unnecessary
&lt;/h2&gt;

&lt;p&gt;Now consider a simple utility workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open tool
Drop image
Convert
Download
Leave
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What persistent state is required?&lt;/p&gt;

&lt;p&gt;Maybe none.&lt;/p&gt;

&lt;p&gt;If the tool can deliver value immediately, forcing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name → email → password → verification
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;before the first successful result is pure friction.&lt;/p&gt;

&lt;p&gt;The user did not arrive because they wanted a relationship with your SaaS.&lt;/p&gt;

&lt;p&gt;They arrived because they wanted a file converted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "capture the email" instinct
&lt;/h2&gt;

&lt;p&gt;This is where SaaS incentives can fight user incentives.&lt;/p&gt;

&lt;p&gt;The business wants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;leads&lt;/li&gt;
&lt;li&gt;accounts&lt;/li&gt;
&lt;li&gt;retargeting&lt;/li&gt;
&lt;li&gt;upgrade opportunities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The user wants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you optimize only for account creation, you can accidentally reduce the number of people who ever experience the product's value.&lt;/p&gt;

&lt;p&gt;That is a bad trade.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let value come first
&lt;/h2&gt;

&lt;p&gt;A better model for some utilities is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Try core workflow
   ↓
Get successful result
   ↓
Offer account for persistent benefits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now registration has context.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Create an account to continue."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;you can say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Create an account if you want history, analytics, saved settings, or higher limits."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That feels much more honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  This changed how I think about BatchSet
&lt;/h2&gt;

&lt;p&gt;BatchSet includes browser-based image and marketing utilities.&lt;/p&gt;

&lt;p&gt;For common image conversion, I did not want account creation to be the first task.&lt;/p&gt;

&lt;p&gt;The user should be able to understand the product by using it.&lt;/p&gt;

&lt;p&gt;That is why the basic workflow emphasizes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no mandatory signup for common conversion&lt;/li&gt;
&lt;li&gt;no watermark&lt;/li&gt;
&lt;li&gt;browser-based processing for supported basic conversions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see the current product here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://batchset.com/" rel="noopener noreferrer"&gt;BatchSet&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I am the person building it, so this is not a neutral product review. It is a product-design lesson from working on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Friction is not only forms
&lt;/h2&gt;

&lt;p&gt;Signup walls are obvious friction.&lt;/p&gt;

&lt;p&gt;There are quieter forms too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;too many settings before first action&lt;/li&gt;
&lt;li&gt;unclear file limits&lt;/li&gt;
&lt;li&gt;fake "free" buttons&lt;/li&gt;
&lt;li&gt;surprise watermarks&lt;/li&gt;
&lt;li&gt;download gates&lt;/li&gt;
&lt;li&gt;forced tutorials&lt;/li&gt;
&lt;li&gt;unnecessary onboarding modals&lt;/li&gt;
&lt;li&gt;slow confirmation steps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each one delays the moment where the user says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Yes, this solved my problem."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The activation event matters
&lt;/h2&gt;

&lt;p&gt;Every product has some version of an activation event.&lt;/p&gt;

&lt;p&gt;For a file converter, it may be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Successfully download the converted file.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a project-management product:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Create the first project and invite a teammate.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For an analytics product:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Connect a data source and see the first useful dashboard.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your onboarding should help users reach that event.&lt;/p&gt;

&lt;p&gt;If a signup form is required to support it, fine.&lt;/p&gt;

&lt;p&gt;If the signup form only delays it, reconsider.&lt;/p&gt;

&lt;h2&gt;
  
  
  A useful experiment
&lt;/h2&gt;

&lt;p&gt;Take your current funnel and write every step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Visit landing page
Click CTA
Choose plan
Create account
Verify email
Complete onboarding
Upload file
Configure settings
Process
Download
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now ask of every step:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does this help the user reach value, or does it only help us?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Some business-required steps are unavoidable.&lt;/p&gt;

&lt;p&gt;But the question is still worth asking.&lt;/p&gt;

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

&lt;p&gt;Not every SaaS should remove registration.&lt;/p&gt;

&lt;p&gt;But every SaaS should justify registration.&lt;/p&gt;

&lt;p&gt;"Because SaaS products have accounts" is not a strong reason.&lt;/p&gt;

&lt;p&gt;The best onboarding is not the one that collects the most information.&lt;/p&gt;

&lt;p&gt;It is the one that gets the right user to a meaningful result with the least unnecessary friction.&lt;/p&gt;

</description>
      <category>saas</category>
      <category>ux</category>
      <category>buildinpublic</category>
      <category>product</category>
    </item>
    <item>
      <title>The Moment Your Tool Supports 100 Files, It Becomes a Different Product</title>
      <dc:creator>Muhaymin Bin Mehmood</dc:creator>
      <pubDate>Fri, 11 Sep 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/muhayminbinmehmood/the-moment-your-tool-supports-100-files-it-becomes-a-different-product-23jh</link>
      <guid>https://dev.to/muhayminbinmehmood/the-moment-your-tool-supports-100-files-it-becomes-a-different-product-23jh</guid>
      <description>&lt;p&gt;A tool that processes one file and a tool that processes 100 files may share the same conversion function.&lt;/p&gt;

&lt;p&gt;They are still different products.&lt;/p&gt;

&lt;p&gt;The moment users can drop a large batch into your app, almost every surrounding decision changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  One-file logic
&lt;/h2&gt;

&lt;p&gt;A one-file flow can be brutally simple:&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="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;processFile&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="nf"&gt;download&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI can show a spinner.&lt;/p&gt;

&lt;p&gt;If the operation fails, show an error.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now add 100 files
&lt;/h2&gt;

&lt;p&gt;The naive implementation becomes:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&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;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;processFile&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 looks elegant.&lt;/p&gt;

&lt;p&gt;It can also be a terrible idea.&lt;/p&gt;

&lt;p&gt;If every file is a large image, you may suddenly decode dozens or hundreds of high-resolution bitmaps simultaneously.&lt;/p&gt;

&lt;p&gt;Memory spikes.&lt;/p&gt;

&lt;p&gt;The tab freezes.&lt;/p&gt;

&lt;p&gt;Mobile devices suffer.&lt;/p&gt;

&lt;p&gt;The browser may kill the page.&lt;/p&gt;

&lt;p&gt;Bulk support requires &lt;strong&gt;resource management&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controlled concurrency
&lt;/h2&gt;

&lt;p&gt;A better design uses a limited number of active jobs.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Queue: 100 files

Active:
[1] processing
[2] processing
[3] processing
[4] processing

When [2] finishes:
take next item from queue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a lever.&lt;/p&gt;

&lt;p&gt;You can adjust throughput without creating unbounded pressure.&lt;/p&gt;

&lt;p&gt;The correct limit depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;average file size&lt;/li&gt;
&lt;li&gt;image dimensions&lt;/li&gt;
&lt;li&gt;CPU cost&lt;/li&gt;
&lt;li&gt;memory usage&lt;/li&gt;
&lt;li&gt;target device class&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important principle is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Maximum parallelism is not the same thing as maximum useful performance.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Progress becomes a first-class feature
&lt;/h2&gt;

&lt;p&gt;With one file, a spinner is enough.&lt;/p&gt;

&lt;p&gt;With 100 files, the user wants answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many are complete?&lt;/li&gt;
&lt;li&gt;Which file is processing?&lt;/li&gt;
&lt;li&gt;Did any fail?&lt;/li&gt;
&lt;li&gt;Can I cancel?&lt;/li&gt;
&lt;li&gt;Can I download completed outputs?&lt;/li&gt;
&lt;li&gt;Is the app stuck?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful progress model might show:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;63 / 100 complete
4 processing
31 waiting
2 failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is much more informative than an indeterminate loader.&lt;/p&gt;

&lt;h2&gt;
  
  
  Errors should not kill the batch
&lt;/h2&gt;

&lt;p&gt;Another difference:&lt;/p&gt;

&lt;p&gt;If 1 file fails out of 100, should the entire job fail?&lt;/p&gt;

&lt;p&gt;Usually not.&lt;/p&gt;

&lt;p&gt;Bulk operations need per-item error isolation.&lt;/p&gt;

&lt;p&gt;A better result can look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ photo-001.jpg
✓ photo-002.jpg
✗ broken-file.tiff
✓ photo-004.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the user can retry or inspect only the failed items.&lt;/p&gt;

&lt;p&gt;This is a general batch-processing principle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;One bad record should not destroy 99 good results unless atomicity is actually required.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Download UX changes too
&lt;/h2&gt;

&lt;p&gt;Downloading 100 files one by one is technically possible.&lt;/p&gt;

&lt;p&gt;It is also horrible UX.&lt;/p&gt;

&lt;p&gt;Users expect one packaged output.&lt;/p&gt;

&lt;p&gt;That is why ZIP generation naturally appears in bulk tools.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;process items
   ↓
collect outputs
   ↓
package
   ↓
download once
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The packaging step becomes part of the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  Naming becomes important
&lt;/h2&gt;

&lt;p&gt;Single-file tools can get away with generic output names.&lt;/p&gt;

&lt;p&gt;Bulk tools cannot.&lt;/p&gt;

&lt;p&gt;If the user uploads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;product-01.jpg
product-02.jpg
product-03.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the outputs should remain recognizable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;product-01.webp
product-02.webp
product-03.webp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise the user saves time during conversion and loses it again during file cleanup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cancellation matters
&lt;/h2&gt;

&lt;p&gt;A 500-file job may take long enough that the user changes their mind.&lt;/p&gt;

&lt;p&gt;A production-quality system should think about cancellation.&lt;/p&gt;

&lt;p&gt;In browser code, that may involve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;abort signals&lt;/li&gt;
&lt;li&gt;worker termination&lt;/li&gt;
&lt;li&gt;queue state&lt;/li&gt;
&lt;li&gt;cleanup of temporary objects&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The UX should clearly distinguish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;queued&lt;/li&gt;
&lt;li&gt;active&lt;/li&gt;
&lt;li&gt;completed&lt;/li&gt;
&lt;li&gt;cancelled&lt;/li&gt;
&lt;li&gt;failed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Memory cleanup is part of correctness
&lt;/h2&gt;

&lt;p&gt;Browser image processing often creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ImageBitmap&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;canvas buffers&lt;/li&gt;
&lt;li&gt;object URLs&lt;/li&gt;
&lt;li&gt;blobs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you keep unnecessary references alive, memory usage can climb during a large batch.&lt;/p&gt;

&lt;p&gt;Bulk testing should include long runs, not just successful 5-file demos.&lt;/p&gt;

&lt;h2&gt;
  
  
  This problem shaped BatchSet
&lt;/h2&gt;

&lt;p&gt;One of the reasons I built &lt;a href="https://batchset.com/tools/bulk-image-converter" rel="noopener noreferrer"&gt;BatchSet's Bulk Image Converter&lt;/a&gt; was that I wanted the workflow to focus on the batch as a unit.&lt;/p&gt;

&lt;p&gt;Not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Here's a converter that technically accepts multiple files."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"How should the app behave when the batch itself is the user's job?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That framing changes the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist for bulk tools
&lt;/h2&gt;

&lt;p&gt;If you are adding multi-file support, I would review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] controlled concurrency&lt;/li&gt;
&lt;li&gt;[ ] per-item status&lt;/li&gt;
&lt;li&gt;[ ] total progress&lt;/li&gt;
&lt;li&gt;[ ] failure isolation&lt;/li&gt;
&lt;li&gt;[ ] retry behavior&lt;/li&gt;
&lt;li&gt;[ ] cancellation&lt;/li&gt;
&lt;li&gt;[ ] predictable filenames&lt;/li&gt;
&lt;li&gt;[ ] memory cleanup&lt;/li&gt;
&lt;li&gt;[ ] one-click packaged download&lt;/li&gt;
&lt;li&gt;[ ] mobile / low-memory testing&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;"Supports multiple files" is not a checkbox.&lt;/p&gt;

&lt;p&gt;It is an architecture decision.&lt;/p&gt;

&lt;p&gt;The processing function may be the same.&lt;/p&gt;

&lt;p&gt;Everything around it changes.&lt;/p&gt;

&lt;p&gt;And that is why the moment your app supports 100 files, it becomes a different product.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>performance</category>
      <category>ux</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Code 128 vs EAN-13 vs UPC-A: The Barcode Question People Ask Too Late</title>
      <dc:creator>Muhaymin Bin Mehmood</dc:creator>
      <pubDate>Thu, 10 Sep 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/muhayminbinmehmood/code-128-vs-ean-13-vs-upc-a-the-barcode-question-people-ask-too-late-nn7</link>
      <guid>https://dev.to/muhayminbinmehmood/code-128-vs-ean-13-vs-upc-a-the-barcode-question-people-ask-too-late-nn7</guid>
      <description>&lt;p&gt;A barcode generator can create an image in seconds.&lt;/p&gt;

&lt;p&gt;That is not the hard part.&lt;/p&gt;

&lt;p&gt;The hard part is answering:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What type of barcode should this number become?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you choose the barcode format after designing labels, building inventory screens, and printing packaging, you are asking the question too late.&lt;/p&gt;

&lt;p&gt;Three formats appear constantly in practical workflows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UPC-A&lt;/li&gt;
&lt;li&gt;EAN-13&lt;/li&gt;
&lt;li&gt;Code 128&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They solve different problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  UPC-A
&lt;/h2&gt;

&lt;p&gt;UPC-A is strongly associated with retail product identification, especially in North American contexts.&lt;/p&gt;

&lt;p&gt;It represents numeric data in a fixed structure.&lt;/p&gt;

&lt;p&gt;If you are dealing with retail products intended for established point-of-sale ecosystems, UPC may be part of the required workflow.&lt;/p&gt;

&lt;p&gt;Important:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Generating a UPC-looking barcode does not mean you have created a legitimate registered retail product identifier.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The number itself and its ownership/registration are separate from rendering the bars.&lt;/p&gt;

&lt;h2&gt;
  
  
  EAN-13
&lt;/h2&gt;

&lt;p&gt;EAN-13 is also used for retail product identification and is common internationally.&lt;/p&gt;

&lt;p&gt;Like UPC, it is not just "a barcode style."&lt;/p&gt;

&lt;p&gt;It participates in a broader product-identification system.&lt;/p&gt;

&lt;p&gt;This distinction matters because developers sometimes build a generator that accepts any 13 digits and assume the business problem is solved.&lt;/p&gt;

&lt;p&gt;Technically, the image may be valid.&lt;/p&gt;

&lt;p&gt;Operationally, the identifier may not be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code 128
&lt;/h2&gt;

&lt;p&gt;Code 128 is more flexible for internal and logistics workflows.&lt;/p&gt;

&lt;p&gt;It can represent a broader set of characters and is commonly useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;warehouse labels&lt;/li&gt;
&lt;li&gt;internal stock IDs&lt;/li&gt;
&lt;li&gt;shipment references&lt;/li&gt;
&lt;li&gt;bin locations&lt;/li&gt;
&lt;li&gt;custom inventory systems&lt;/li&gt;
&lt;li&gt;operational tracking&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you control both the system generating the identifier and the system scanning it, Code 128 can be a practical option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retail identity vs internal identity
&lt;/h2&gt;

&lt;p&gt;This is the easiest mental model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retail barcode
&lt;/h3&gt;

&lt;p&gt;The identifier needs to participate in a standardized external ecosystem.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;UPC-A&lt;/li&gt;
&lt;li&gt;EAN-13&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Internal barcode
&lt;/h3&gt;

&lt;p&gt;The identifier mainly needs to work inside your own operational system.&lt;/p&gt;

&lt;p&gt;Example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code 128&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That distinction often tells you more than a long barcode-spec comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check digits are not random
&lt;/h2&gt;

&lt;p&gt;UPC and EAN systems commonly include a check digit.&lt;/p&gt;

&lt;p&gt;The purpose is error detection.&lt;/p&gt;

&lt;p&gt;If a digit is mistyped or scanned incorrectly, the checksum can help identify that the value is invalid.&lt;/p&gt;

&lt;p&gt;This is useful because retail scanning needs to be reliable at high speed.&lt;/p&gt;

&lt;p&gt;The exact calculation is deterministic.&lt;/p&gt;

&lt;p&gt;That means a good barcode tool should not simply draw bars.&lt;/p&gt;

&lt;p&gt;It should validate the structure where appropriate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The generator is not the registry
&lt;/h2&gt;

&lt;p&gt;This deserves repeating because it causes real confusion.&lt;/p&gt;

&lt;p&gt;A barcode generator can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;render a valid symbol&lt;/li&gt;
&lt;li&gt;calculate or validate certain checksums&lt;/li&gt;
&lt;li&gt;export a label image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It cannot automatically assign your business a globally recognized retail product number.&lt;/p&gt;

&lt;p&gt;That comes from the relevant numbering/registration process.&lt;/p&gt;

&lt;p&gt;Think of it like a QR code generator.&lt;/p&gt;

&lt;p&gt;The QR tool can encode a URL.&lt;/p&gt;

&lt;p&gt;It does not give you ownership of the domain inside that URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batch generation is where things become useful
&lt;/h2&gt;

&lt;p&gt;Generating one barcode is simple.&lt;/p&gt;

&lt;p&gt;Generating 2,000 warehouse labels is a workflow.&lt;/p&gt;

&lt;p&gt;At that point you care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;importing identifiers&lt;/li&gt;
&lt;li&gt;validating values&lt;/li&gt;
&lt;li&gt;naming outputs&lt;/li&gt;
&lt;li&gt;generating consistently&lt;/li&gt;
&lt;li&gt;printing labels&lt;/li&gt;
&lt;li&gt;matching SKU data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The product problem changes from "draw barcode" to "prepare barcode assets at scale."&lt;/p&gt;

&lt;p&gt;I built a &lt;a href="https://batchset.com/tools/barcode-generator" rel="noopener noreferrer"&gt;Barcode Generator in BatchSet&lt;/a&gt; because I wanted the generator to sit alongside the other e-commerce utilities instead of becoming another isolated website.&lt;/p&gt;

&lt;p&gt;You can also see the broader catalog workflow here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://batchset.com/for/ecommerce" rel="noopener noreferrer"&gt;BatchSet for E-Commerce&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical decision table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Likely direction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Retail product sold through established external systems&lt;/td&gt;
&lt;td&gt;UPC-A or EAN-13 depending on the required standard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal warehouse identifier&lt;/td&gt;
&lt;td&gt;Code 128&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shipment / logistics reference&lt;/td&gt;
&lt;td&gt;Often Code 128 or another logistics-specific standard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arbitrary internal alphanumeric value&lt;/td&gt;
&lt;td&gt;Code 128&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Globally recognized retail identity&lt;/td&gt;
&lt;td&gt;Use the appropriate registered numbering process, not a random generated number&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This table is intentionally simplified.&lt;/p&gt;

&lt;p&gt;Real supply chains can have additional GS1 requirements and industry-specific standards.&lt;/p&gt;

&lt;h2&gt;
  
  
  What developers should ask before implementation
&lt;/h2&gt;

&lt;p&gt;Before building barcode support, ask the business:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Who scans the code?&lt;/li&gt;
&lt;li&gt;What system receives the value?&lt;/li&gt;
&lt;li&gt;Is the identifier internal or externally standardized?&lt;/li&gt;
&lt;li&gt;Does the value already exist?&lt;/li&gt;
&lt;li&gt;Is registration required?&lt;/li&gt;
&lt;li&gt;What printers and scanners are used?&lt;/li&gt;
&lt;li&gt;What label size is available?&lt;/li&gt;
&lt;li&gt;Do we need human-readable text under the barcode?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These questions can prevent weeks of redesign.&lt;/p&gt;

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

&lt;p&gt;Choosing a barcode is not primarily a graphics decision.&lt;/p&gt;

&lt;p&gt;It is an &lt;strong&gt;identity-system decision&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The bars are just the machine-readable representation.&lt;/p&gt;

&lt;p&gt;Start with what the identifier means, who owns it, and where it needs to work.&lt;/p&gt;

&lt;p&gt;Then choose the barcode format.&lt;/p&gt;

</description>
      <category>ecommerce</category>
      <category>programming</category>
      <category>logistics</category>
      <category>barcode</category>
    </item>
    <item>
      <title>A QR Code Without Tracking Is a Missed Experiment</title>
      <dc:creator>Muhaymin Bin Mehmood</dc:creator>
      <pubDate>Wed, 09 Sep 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/muhayminbinmehmood/a-qr-code-without-tracking-is-a-missed-experiment-8ff</link>
      <guid>https://dev.to/muhayminbinmehmood/a-qr-code-without-tracking-is-a-missed-experiment-8ff</guid>
      <description>&lt;p&gt;QR codes are everywhere again.&lt;/p&gt;

&lt;p&gt;Menus.&lt;/p&gt;

&lt;p&gt;Packaging.&lt;/p&gt;

&lt;p&gt;Flyers.&lt;/p&gt;

&lt;p&gt;Booths.&lt;/p&gt;

&lt;p&gt;Business cards.&lt;/p&gt;

&lt;p&gt;Posters.&lt;/p&gt;

&lt;p&gt;But a lot of QR campaigns stop at:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;QR → homepage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works technically.&lt;/p&gt;

&lt;p&gt;It is weak analytically.&lt;/p&gt;

&lt;p&gt;If you are printing 10,000 flyers, you should be able to answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did those flyers generate meaningful traffic?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That requires thinking beyond the QR image.&lt;/p&gt;

&lt;h2&gt;
  
  
  The QR code is only the transport layer
&lt;/h2&gt;

&lt;p&gt;A QR code stores or represents data.&lt;/p&gt;

&lt;p&gt;In a marketing campaign, that data is usually a URL.&lt;/p&gt;

&lt;p&gt;The useful architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Campaign destination
        ↓
Tracking parameters
        ↓
Short / managed URL
        ↓
QR code
        ↓
Printed material
        ↓
Scan
        ↓
Analytics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The QR graphic itself is not the analytics strategy.&lt;/p&gt;

&lt;p&gt;The URL is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with UTMs
&lt;/h2&gt;

&lt;p&gt;A simple campaign URL might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://example.com/pricing
  ?utm_source=flyer
  &amp;amp;utm_medium=qr
  &amp;amp;utm_campaign=september_launch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now analytics can distinguish that traffic from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;organic search&lt;/li&gt;
&lt;li&gt;LinkedIn&lt;/li&gt;
&lt;li&gt;email&lt;/li&gt;
&lt;li&gt;direct visits&lt;/li&gt;
&lt;li&gt;another offline campaign&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exact naming convention matters less than consistency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use a naming system
&lt;/h2&gt;

&lt;p&gt;Do not create this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;utm_campaign=flyer1
utm_campaign=newflyer
utm_campaign=septthing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A predictable structure is much easier to analyze.&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 plaintext"&gt;&lt;code&gt;utm_source=offline
utm_medium=qr
utm_campaign=2026_september_launch
utm_content=store_counter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then another placement can be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;utm_content=product_packaging
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and another:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;utm_content=trade_show_banner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the campaign is comparable by placement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why short links help
&lt;/h2&gt;

&lt;p&gt;UTM URLs are ugly.&lt;/p&gt;

&lt;p&gt;A raw tracked URL can become very long.&lt;/p&gt;

&lt;p&gt;The QR code can still encode it, but a managed short URL gives you useful flexibility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;short URL
   ↓
tracked destination
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on the system, you may be able to change the destination later without replacing the printed visual.&lt;/p&gt;

&lt;p&gt;Even when you cannot, short links are easier to manage and communicate.&lt;/p&gt;

&lt;p&gt;They are also useful when the same campaign link is shared in text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate campaigns by physical placement
&lt;/h2&gt;

&lt;p&gt;This is where the data becomes interesting.&lt;/p&gt;

&lt;p&gt;Imagine a coffee shop has three QR placements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;table tent&lt;/li&gt;
&lt;li&gt;receipt&lt;/li&gt;
&lt;li&gt;front-door poster&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If all three use the same QR URL, you only know total scans.&lt;/p&gt;

&lt;p&gt;If each uses a distinct tracked link, you can learn which placement actually works.&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 plaintext"&gt;&lt;code&gt;utm_content=table_tent
utm_content=receipt
utm_content=door_poster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the physical world becomes testable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not send every QR to the homepage
&lt;/h2&gt;

&lt;p&gt;This is a common mistake.&lt;/p&gt;

&lt;p&gt;The scan should continue the user's context.&lt;/p&gt;

&lt;p&gt;A QR on a product package might go to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;setup instructions&lt;/li&gt;
&lt;li&gt;warranty registration&lt;/li&gt;
&lt;li&gt;reorder page&lt;/li&gt;
&lt;li&gt;authenticity verification&lt;/li&gt;
&lt;li&gt;product tutorial&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A QR at an event might go to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;demo booking&lt;/li&gt;
&lt;li&gt;event-specific landing page&lt;/li&gt;
&lt;li&gt;downloadable resource&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A QR in a restaurant might go to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;menu&lt;/li&gt;
&lt;li&gt;feedback form&lt;/li&gt;
&lt;li&gt;loyalty signup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The destination should match the reason someone scanned.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test before printing
&lt;/h2&gt;

&lt;p&gt;A print mistake is expensive.&lt;/p&gt;

&lt;p&gt;Before approving a QR asset:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scan it on multiple phones&lt;/li&gt;
&lt;li&gt;test normal and low-light conditions&lt;/li&gt;
&lt;li&gt;verify the URL&lt;/li&gt;
&lt;li&gt;verify UTM parameters&lt;/li&gt;
&lt;li&gt;verify redirect behavior&lt;/li&gt;
&lt;li&gt;test the final physical size&lt;/li&gt;
&lt;li&gt;leave sufficient contrast and quiet space&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A QR that works at 800px on your monitor may fail when printed tiny on packaging.&lt;/p&gt;

&lt;h2&gt;
  
  
  A workflow I am building
&lt;/h2&gt;

&lt;p&gt;I wanted QR generation and link tracking to live closer together, because marketers often need both.&lt;/p&gt;

&lt;p&gt;So BatchSet includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://batchset.com/tools/qr-code-generator" rel="noopener noreferrer"&gt;QR Code Generator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://batchset.com/tools/url-shortener" rel="noopener noreferrer"&gt;URL Shortener&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the marketing-focused collection is here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://batchset.com/for/marketers" rel="noopener noreferrer"&gt;BatchSet for Marketers&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I build BatchSet, so this is the transparent product mention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Suggested UTM template
&lt;/h2&gt;

&lt;p&gt;If you need a starting convention:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;utm_source=&amp;lt;channel-or-placement&amp;gt;
utm_medium=qr
utm_campaign=&amp;lt;year_month_campaign&amp;gt;
utm_content=&amp;lt;creative-or-location&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;utm_source=conference
utm_medium=qr
utm_campaign=2026_09_product_launch
utm_content=booth_banner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that your team uses the same system repeatedly.&lt;/p&gt;

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

&lt;p&gt;Offline marketing does not have to be unmeasurable.&lt;/p&gt;

&lt;p&gt;A QR code can connect physical material to digital behavior.&lt;/p&gt;

&lt;p&gt;But only if you design the tracking before you print.&lt;/p&gt;

&lt;p&gt;So next time you generate a QR code, do not stop at:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Does it scan?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Also ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Will we know what happened after it scanned?"&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>marketing</category>
      <category>analytics</category>
      <category>webdev</category>
      <category>qrcode</category>
    </item>
    <item>
      <title>One Design, 28 Outputs: The Engineering Problem Behind a Social Media Resizer</title>
      <dc:creator>Muhaymin Bin Mehmood</dc:creator>
      <pubDate>Tue, 08 Sep 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/muhayminbinmehmood/one-design-28-outputs-the-engineering-problem-behind-a-social-media-resizer-2pld</link>
      <guid>https://dev.to/muhayminbinmehmood/one-design-28-outputs-the-engineering-problem-behind-a-social-media-resizer-2pld</guid>
      <description>&lt;p&gt;A social media resizer sounds like a tiny utility.&lt;/p&gt;

&lt;p&gt;Upload an image.&lt;/p&gt;

&lt;p&gt;Pick Instagram.&lt;/p&gt;

&lt;p&gt;Resize.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;Then you try to support multiple platforms and discover the real problem.&lt;/p&gt;

&lt;p&gt;The problem is not resizing.&lt;/p&gt;

&lt;p&gt;The problem is &lt;strong&gt;turning one source design into many different aspect ratios without making the outputs look broken&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with presets as data
&lt;/h2&gt;

&lt;p&gt;Do not hard-code every platform into UI logic.&lt;/p&gt;

&lt;p&gt;A better design starts with a preset model.&lt;/p&gt;

&lt;p&gt;Conceptually:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;presets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;instagram-portrait&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Instagram&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Portrait Post&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1350&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tiktok-vertical&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TikTok&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Vertical&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1920&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 UI is generated from data.&lt;/p&gt;

&lt;p&gt;That makes it easier to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;add new presets&lt;/li&gt;
&lt;li&gt;update dimensions&lt;/li&gt;
&lt;li&gt;group by platform&lt;/li&gt;
&lt;li&gt;search presets&lt;/li&gt;
&lt;li&gt;export multiple targets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The preset list becomes product configuration rather than scattered conditionals.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hard part: aspect ratios
&lt;/h2&gt;

&lt;p&gt;Suppose the source is a wide 16:9 graphic.&lt;/p&gt;

&lt;p&gt;Now the user asks for a 9:16 vertical output.&lt;/p&gt;

&lt;p&gt;You have three basic choices.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Contain
&lt;/h3&gt;

&lt;p&gt;Scale the whole source until it fits.&lt;/p&gt;

&lt;p&gt;Result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;nothing is cropped&lt;/li&gt;
&lt;li&gt;empty space may appear&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Cover
&lt;/h3&gt;

&lt;p&gt;Scale until the entire target is filled.&lt;/p&gt;

&lt;p&gt;Result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no empty space&lt;/li&gt;
&lt;li&gt;part of the source is cropped&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Stretch
&lt;/h3&gt;

&lt;p&gt;Force the source to exactly match the target.&lt;/p&gt;

&lt;p&gt;Result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no empty space&lt;/li&gt;
&lt;li&gt;no crop&lt;/li&gt;
&lt;li&gt;distorted content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most creative work, stretch is the dangerous one.&lt;/p&gt;

&lt;p&gt;Humans notice distorted faces, logos, circles, and product shapes immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crop position becomes a UX feature
&lt;/h2&gt;

&lt;p&gt;"Cover" is not enough.&lt;/p&gt;

&lt;p&gt;You also need to decide &lt;strong&gt;which part gets cropped&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Center crop is a reasonable default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[     SOURCE IMAGE     ]

        [TARGET]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it fails when the important content is near an edge.&lt;/p&gt;

&lt;p&gt;A better resizer lets the user:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;drag the image&lt;/li&gt;
&lt;li&gt;zoom&lt;/li&gt;
&lt;li&gt;reposition&lt;/li&gt;
&lt;li&gt;preview output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the app is not just calculating dimensions.&lt;/p&gt;

&lt;p&gt;It is helping the user preserve visual intent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safe areas complicate things further
&lt;/h2&gt;

&lt;p&gt;A 1080×1920 canvas does not mean every pixel is equally useful.&lt;/p&gt;

&lt;p&gt;Platform UI can overlap portions of vertical content:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;captions&lt;/li&gt;
&lt;li&gt;buttons&lt;/li&gt;
&lt;li&gt;usernames&lt;/li&gt;
&lt;li&gt;navigation&lt;/li&gt;
&lt;li&gt;device chrome&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So a more advanced resizer can visualize a "safe" region where important text and logos should stay.&lt;/p&gt;

&lt;p&gt;This is not strictly part of image resizing.&lt;/p&gt;

&lt;p&gt;It is part of &lt;strong&gt;platform-aware composition&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-output changes the architecture
&lt;/h2&gt;

&lt;p&gt;Generating one output can be synchronous and simple.&lt;/p&gt;

&lt;p&gt;Generating 20+ outputs changes the UX.&lt;/p&gt;

&lt;p&gt;The user now expects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;progress&lt;/li&gt;
&lt;li&gt;predictable filenames&lt;/li&gt;
&lt;li&gt;grouped results&lt;/li&gt;
&lt;li&gt;one download&lt;/li&gt;
&lt;li&gt;consistent crop rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A filename scheme might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;campaign-instagram-portrait.jpg
campaign-linkedin-post.jpg
campaign-tiktok-vertical.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of making the user download each file individually, bundle them.&lt;/p&gt;

&lt;p&gt;That is why ZIP export becomes a workflow feature, not a convenience.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I think about the pipeline
&lt;/h2&gt;

&lt;p&gt;A clean pipeline can look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source image
    ↓
Decode once
    ↓
For each selected preset:
    - calculate scale
    - apply crop position
    - draw to canvas
    - export blob
    ↓
Package outputs
    ↓
Download ZIP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key idea is to avoid redoing unnecessary work.&lt;/p&gt;

&lt;p&gt;Decode once.&lt;/p&gt;

&lt;p&gt;Reuse source state.&lt;/p&gt;

&lt;p&gt;Generate targets from a shared transformation model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quality settings matter too
&lt;/h2&gt;

&lt;p&gt;Different social assets have different needs.&lt;/p&gt;

&lt;p&gt;A photographic post can tolerate lossy compression.&lt;/p&gt;

&lt;p&gt;A graphic with small text may need a higher quality setting.&lt;/p&gt;

&lt;p&gt;The resizer should avoid making destructive choices invisible.&lt;/p&gt;

&lt;p&gt;Users should understand that smaller output files can trade away image quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  I built this because the manual workflow is ridiculous
&lt;/h2&gt;

&lt;p&gt;The real-world process often looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open Canva/Figma/Photoshop
Duplicate canvas
Resize
Fix crop
Export
Repeat
Repeat
Repeat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The creative decision was already made.&lt;/p&gt;

&lt;p&gt;The remaining work is mechanical.&lt;/p&gt;

&lt;p&gt;That is exactly the kind of task software should compress.&lt;/p&gt;

&lt;p&gt;I built a version of this workflow into the &lt;a href="https://batchset.com/tools/social-media-resizer" rel="noopener noreferrer"&gt;BatchSet Social Media Resizer&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It currently focuses on turning one source image into platform-specific outputs without forcing the user to rebuild the design manually.&lt;/p&gt;

&lt;p&gt;You can also see the broader marketing workflow here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://batchset.com/for/marketers" rel="noopener noreferrer"&gt;BatchSet for Marketers&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A useful product principle
&lt;/h2&gt;

&lt;p&gt;When a user repeats the same transformation with different parameters, do not just build a faster single-action tool.&lt;/p&gt;

&lt;p&gt;Ask whether the product should understand the &lt;strong&gt;set of outputs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is the difference between:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"resize image"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"prepare this campaign for every channel"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The second framing produces a much more useful product.&lt;/p&gt;

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

&lt;p&gt;A social media resizer is a good example of how simple utilities hide interesting engineering problems.&lt;/p&gt;

&lt;p&gt;The pixel math is easy.&lt;/p&gt;

&lt;p&gt;The real work is in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;aspect-ratio behavior&lt;/li&gt;
&lt;li&gt;crop control&lt;/li&gt;
&lt;li&gt;preset maintenance&lt;/li&gt;
&lt;li&gt;output naming&lt;/li&gt;
&lt;li&gt;batch generation&lt;/li&gt;
&lt;li&gt;safe-area UX&lt;/li&gt;
&lt;li&gt;download packaging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is where a small tool becomes a real workflow.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>frontend</category>
      <category>design</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Next.js Image Optimization Starts Before &lt;Image /&gt;</title>
      <dc:creator>Muhaymin Bin Mehmood</dc:creator>
      <pubDate>Mon, 07 Sep 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/muhayminbinmehmood/nextjs-image-optimization-starts-before-41bp</link>
      <guid>https://dev.to/muhayminbinmehmood/nextjs-image-optimization-starts-before-41bp</guid>
      <description>&lt;p&gt;Next.js gives us a powerful &lt;code&gt;&amp;lt;Image /&amp;gt;&lt;/code&gt; component.&lt;/p&gt;

&lt;p&gt;That is good.&lt;/p&gt;

&lt;p&gt;But it can create a false sense that image optimization is now "handled."&lt;/p&gt;

&lt;p&gt;It is not.&lt;/p&gt;

&lt;p&gt;The component helps with delivery.&lt;/p&gt;

&lt;p&gt;Your &lt;strong&gt;source-asset workflow&lt;/strong&gt; still matters.&lt;/p&gt;

&lt;p&gt;If your repository, CMS, or product catalog contains chaotic 6000px images with inconsistent crops and random filenames, &lt;code&gt;&amp;lt;Image /&amp;gt;&lt;/code&gt; cannot turn that into a clean content pipeline by itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1: Source preparation
&lt;/h2&gt;

&lt;p&gt;Before an image reaches your application, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is this source much larger than necessary?&lt;/li&gt;
&lt;li&gt;Is the crop correct?&lt;/li&gt;
&lt;li&gt;Is transparency needed?&lt;/li&gt;
&lt;li&gt;Does the filename make sense?&lt;/li&gt;
&lt;li&gt;Is the format appropriate?&lt;/li&gt;
&lt;li&gt;Is this one asset or should there be multiple intentional variants?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is content preparation, not framework behavior.&lt;/p&gt;

&lt;p&gt;For a hero image, you may intentionally keep a large source.&lt;/p&gt;

&lt;p&gt;For a small card icon, a huge photographic source makes no sense.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 2: Correct component usage
&lt;/h2&gt;

&lt;p&gt;A basic Next.js example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Image&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;next/image&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="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProductCard&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="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="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;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image&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="si"&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;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;640&lt;/span&gt;&lt;span class="si"&gt;}&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;The important part is not just using &lt;code&gt;&amp;lt;Image /&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The layout needs correct dimensions and the browser needs enough information to choose an appropriate resource.&lt;/p&gt;

&lt;p&gt;For responsive layouts, the &lt;code&gt;sizes&lt;/code&gt; prop matters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&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;product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image&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="si"&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;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;fill&lt;/span&gt;
  &lt;span class="na"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"(max-width: 768px) 50vw, 25vw"&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;Without accurate sizing information, the browser may choose a larger image candidate than necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3: Asset consistency
&lt;/h2&gt;

&lt;p&gt;This is where teams lose time.&lt;/p&gt;

&lt;p&gt;Suppose a product category contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shirt_01.JPG
shirt-2-final.png
IMG_9833.jpeg
newnewshirt.webp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The framework can render them.&lt;/p&gt;

&lt;p&gt;But the content system is messy.&lt;/p&gt;

&lt;p&gt;A better pipeline produces predictable names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shirt-black-front.webp
shirt-black-back.webp
shirt-blue-front.webp
shirt-blue-back.webp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Predictability matters when you later:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generate feeds&lt;/li&gt;
&lt;li&gt;build product imports&lt;/li&gt;
&lt;li&gt;match SKUs&lt;/li&gt;
&lt;li&gt;automate updates&lt;/li&gt;
&lt;li&gt;debug broken references&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Optimization is also organization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 4: Batch work belongs outside the component
&lt;/h2&gt;

&lt;p&gt;If 300 source images need the same conversion, do not solve that inside React component logic.&lt;/p&gt;

&lt;p&gt;Do it once before deployment or upload.&lt;/p&gt;

&lt;p&gt;A batch preparation flow can be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Raw assets
   ↓
Resize / crop
   ↓
Convert format
   ↓
Compress
   ↓
Validate naming
   ↓
Publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes your app consume cleaner inputs.&lt;/p&gt;

&lt;p&gt;For manual batches, I use the &lt;a href="https://batchset.com/tools/bulk-image-converter" rel="noopener noreferrer"&gt;BatchSet Bulk Image Converter&lt;/a&gt;, which I built specifically because processing image collections one file at a time is painful.&lt;/p&gt;

&lt;p&gt;For a fully automated production system, you may eventually move this pipeline into CI, a media service, or your CMS.&lt;/p&gt;

&lt;p&gt;The principle is the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 5: Do not preload everything
&lt;/h2&gt;

&lt;p&gt;Another common mistake is treating every image as high priority.&lt;/p&gt;

&lt;p&gt;If everything is important, nothing is important.&lt;/p&gt;

&lt;p&gt;Above-the-fold hero imagery may deserve early loading.&lt;/p&gt;

&lt;p&gt;Images far below the fold generally do not.&lt;/p&gt;

&lt;p&gt;Your application should reflect actual visual priority.&lt;/p&gt;

&lt;p&gt;Avoid turning every card into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&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;priority&lt;/span&gt; &lt;span class="err"&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;just because it seems faster in one local test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 6: Measure with real content
&lt;/h2&gt;

&lt;p&gt;Placeholder images lie.&lt;/p&gt;

&lt;p&gt;A page tested with six tiny sample assets can behave very differently after the marketing team uploads the real catalog.&lt;/p&gt;

&lt;p&gt;Performance testing should include realistic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;file dimensions&lt;/li&gt;
&lt;li&gt;file sizes&lt;/li&gt;
&lt;li&gt;image counts&lt;/li&gt;
&lt;li&gt;device widths&lt;/li&gt;
&lt;li&gt;network conditions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially important for e-commerce grids.&lt;/p&gt;

&lt;p&gt;Twenty optimized images can still create a meaningful transfer and rendering cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  A workflow I recommend
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before content enters the app
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Remove obviously oversized sources.&lt;/li&gt;
&lt;li&gt;Normalize crop/aspect ratio when design requires it.&lt;/li&gt;
&lt;li&gt;Convert to an appropriate modern format.&lt;/li&gt;
&lt;li&gt;Apply sensible compression.&lt;/li&gt;
&lt;li&gt;Use predictable filenames.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Inside Next.js
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;code&gt;&amp;lt;Image /&amp;gt;&lt;/code&gt; correctly.&lt;/li&gt;
&lt;li&gt;Provide correct dimensions or &lt;code&gt;fill&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Configure &lt;code&gt;sizes&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Prioritize only genuinely important images.&lt;/li&gt;
&lt;li&gt;Measure real pages.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  After deployment
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Check Core Web Vitals.&lt;/li&gt;
&lt;li&gt;Inspect the largest image requests.&lt;/li&gt;
&lt;li&gt;Look for oversized resource selection.&lt;/li&gt;
&lt;li&gt;Revisit the source pipeline if the same problem repeats.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why this separation matters
&lt;/h2&gt;

&lt;p&gt;Developers like solving everything in code.&lt;/p&gt;

&lt;p&gt;But some performance problems are really &lt;strong&gt;content operations problems&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your organization repeatedly uploads enormous, incorrectly cropped assets, adding more frontend logic may only hide the issue.&lt;/p&gt;

&lt;p&gt;Fixing the input workflow can be simpler.&lt;/p&gt;

&lt;p&gt;I collected the browser-based image preparation tools I am building here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://batchset.com/tools" rel="noopener noreferrer"&gt;BatchSet Image Tools&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And for performance-focused use cases:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://batchset.com/for/seo" rel="noopener noreferrer"&gt;BatchSet for SEO&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Next.js can optimize image delivery.&lt;/p&gt;

&lt;p&gt;It cannot decide what your asset pipeline should be.&lt;/p&gt;

&lt;p&gt;The strongest setup combines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clean source assets
+
correct Next.js implementation
+
real performance measurement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the difference between using an image component and having an image strategy.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>webperf</category>
      <category>webdev</category>
    </item>
    <item>
      <title>WebP vs AVIF in 2026: Stop Asking Which Is Better</title>
      <dc:creator>Muhaymin Bin Mehmood</dc:creator>
      <pubDate>Sun, 06 Sep 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/muhayminbinmehmood/webp-vs-avif-in-2026-stop-asking-which-is-better-li6</link>
      <guid>https://dev.to/muhayminbinmehmood/webp-vs-avif-in-2026-stop-asking-which-is-better-li6</guid>
      <description>&lt;p&gt;"WebP or AVIF?"&lt;/p&gt;

&lt;p&gt;It sounds like a simple technical question.&lt;/p&gt;

&lt;p&gt;It usually is not.&lt;/p&gt;

&lt;p&gt;The answer depends on what you are optimizing for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;smallest possible file size&lt;/li&gt;
&lt;li&gt;encoding speed&lt;/li&gt;
&lt;li&gt;browser coverage&lt;/li&gt;
&lt;li&gt;workflow simplicity&lt;/li&gt;
&lt;li&gt;image type&lt;/li&gt;
&lt;li&gt;quality requirements&lt;/li&gt;
&lt;li&gt;CMS support&lt;/li&gt;
&lt;li&gt;operational complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So instead of asking which format is universally "better," I prefer a more useful question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Which format produces the best result for this website and this workflow?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why WebP remains practical
&lt;/h2&gt;

&lt;p&gt;WebP has become a very comfortable format for modern web workflows.&lt;/p&gt;

&lt;p&gt;It is useful because it can handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;photographic images&lt;/li&gt;
&lt;li&gt;lossy compression&lt;/li&gt;
&lt;li&gt;lossless compression&lt;/li&gt;
&lt;li&gt;transparency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For many teams, WebP's biggest advantage is not a benchmark result.&lt;/p&gt;

&lt;p&gt;It is &lt;strong&gt;operational maturity&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The tools are everywhere.&lt;/p&gt;

&lt;p&gt;CMS support is common.&lt;/p&gt;

&lt;p&gt;Developers understand it.&lt;/p&gt;

&lt;p&gt;Design teams have seen it.&lt;/p&gt;

&lt;p&gt;A WebP workflow is relatively easy to explain and maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why AVIF is attractive
&lt;/h2&gt;

&lt;p&gt;AVIF can produce extremely efficient files, especially for certain photographic content.&lt;/p&gt;

&lt;p&gt;If your priority is squeezing more bytes out of large image-heavy pages, it deserves testing.&lt;/p&gt;

&lt;p&gt;But smaller output does not automatically mean the total workflow is better.&lt;/p&gt;

&lt;p&gt;You should also consider:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;encoding time&lt;/li&gt;
&lt;li&gt;tooling&lt;/li&gt;
&lt;li&gt;fallback behavior&lt;/li&gt;
&lt;li&gt;how your CMS handles it&lt;/li&gt;
&lt;li&gt;whether non-technical teammates can work with the format&lt;/li&gt;
&lt;li&gt;whether your image pipeline supports it consistently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A technically superior file can create an operationally inferior system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use real images, not one benchmark
&lt;/h2&gt;

&lt;p&gt;This is the most important part.&lt;/p&gt;

&lt;p&gt;Do not take a single sample image and build a format policy around it.&lt;/p&gt;

&lt;p&gt;Your production library may contain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;faces&lt;/li&gt;
&lt;li&gt;screenshots&lt;/li&gt;
&lt;li&gt;gradients&lt;/li&gt;
&lt;li&gt;product photos&lt;/li&gt;
&lt;li&gt;text-heavy graphics&lt;/li&gt;
&lt;li&gt;transparent logos&lt;/li&gt;
&lt;li&gt;dark scenes&lt;/li&gt;
&lt;li&gt;noisy photographs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Different content compresses differently.&lt;/p&gt;

&lt;p&gt;A useful test set should represent your actual site.&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 plaintext"&gt;&lt;code&gt;sample/
  product-white-background.jpg
  product-text-label.jpg
  dark-photo.jpg
  screenshot.png
  transparent-logo.png
  gradient-banner.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then export each through the candidate formats and compare:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;file size&lt;/li&gt;
&lt;li&gt;visible artifacts&lt;/li&gt;
&lt;li&gt;decode behavior&lt;/li&gt;
&lt;li&gt;page-level performance&lt;/li&gt;
&lt;li&gt;workflow complexity&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Avoid the "smallest file wins" trap
&lt;/h2&gt;

&lt;p&gt;Imagine this result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JPEG: 310 KB
WebP: 190 KB
AVIF: 155 KB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is tempting to declare AVIF the winner.&lt;/p&gt;

&lt;p&gt;But ask what happens next.&lt;/p&gt;

&lt;p&gt;If AVIF requires a more complicated build step, slower generation, special handling in your CMS, and extra support burden, the 35 KB difference may not matter.&lt;/p&gt;

&lt;p&gt;On a massive image catalog it might matter a lot.&lt;/p&gt;

&lt;p&gt;On a small marketing site it may not.&lt;/p&gt;

&lt;p&gt;Performance decisions should be proportional to the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boring answer is often the correct one
&lt;/h2&gt;

&lt;p&gt;For many sites, a sensible policy can be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use modern formats.
Resize images properly.
Do not ship giant originals.
Compress reasonably.
Measure real pages.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is less exciting than arguing about codecs.&lt;/p&gt;

&lt;p&gt;It is also more likely to improve the site.&lt;/p&gt;

&lt;p&gt;A 4000px AVIF displayed at 400px can still be a bad decision.&lt;/p&gt;

&lt;p&gt;An appropriately sized WebP may beat it in the only metric users care about:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How quickly the useful page becomes visible.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I use for BatchSet workflows
&lt;/h2&gt;

&lt;p&gt;When I am preparing images for common web use, I often start with WebP because the workflow is straightforward.&lt;/p&gt;

&lt;p&gt;For more aggressive optimization, AVIF becomes a candidate worth testing.&lt;/p&gt;

&lt;p&gt;The important part is that I do not want format selection to become a manual one-file process.&lt;/p&gt;

&lt;p&gt;That is why I built bulk conversion into &lt;a href="https://batchset.com/tools/bulk-image-converter" rel="noopener noreferrer"&gt;BatchSet&lt;/a&gt; and collected the performance-oriented workflows under &lt;a href="https://batchset.com/for/seo" rel="noopener noreferrer"&gt;BatchSet for SEO&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Those links are to a product I build, so consider this the transparent "tool I use" section.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple decision framework
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choose WebP first when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;you want a broadly practical modern format&lt;/li&gt;
&lt;li&gt;your team needs a simple workflow&lt;/li&gt;
&lt;li&gt;your CMS supports it cleanly&lt;/li&gt;
&lt;li&gt;you want transparency support&lt;/li&gt;
&lt;li&gt;you need easy batch conversion&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Test AVIF when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;image transfer size is a major bottleneck&lt;/li&gt;
&lt;li&gt;your stack handles it well&lt;/li&gt;
&lt;li&gt;you have large image-heavy pages&lt;/li&gt;
&lt;li&gt;you can automate generation&lt;/li&gt;
&lt;li&gt;you are willing to validate quality across browsers and assets&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Keep PNG when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;you need lossless graphics and your workflow requires it&lt;/li&gt;
&lt;li&gt;transparency and exact pixel behavior matter&lt;/li&gt;
&lt;li&gt;the file is small enough that conversion adds no meaningful value&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Keep JPEG when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;compatibility or external workflow constraints require it&lt;/li&gt;
&lt;li&gt;the source is already well optimized&lt;/li&gt;
&lt;li&gt;converting creates no meaningful improvement&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Measure the page, not just the image
&lt;/h2&gt;

&lt;p&gt;This is the part developers often skip.&lt;/p&gt;

&lt;p&gt;You can save 50 KB on an image and still have a slow page because of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;blocking JavaScript&lt;/li&gt;
&lt;li&gt;render delays&lt;/li&gt;
&lt;li&gt;font loading&lt;/li&gt;
&lt;li&gt;third-party scripts&lt;/li&gt;
&lt;li&gt;poor caching&lt;/li&gt;
&lt;li&gt;oversized DOM&lt;/li&gt;
&lt;li&gt;slow backend responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Image format is one lever.&lt;/p&gt;

&lt;p&gt;It is not the entire performance strategy.&lt;/p&gt;

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

&lt;p&gt;WebP vs AVIF is not a championship fight.&lt;/p&gt;

&lt;p&gt;They are tools.&lt;/p&gt;

&lt;p&gt;The correct format is the one that gives you the best balance of:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;quality + size + compatibility + maintainability&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;for your actual project.&lt;/p&gt;

&lt;p&gt;Test representative files.&lt;/p&gt;

&lt;p&gt;Automate the winning workflow.&lt;/p&gt;

&lt;p&gt;Then spend your remaining performance budget on the problems your users can actually feel.&lt;/p&gt;

&lt;p&gt;If you want a browser-based place to experiment with image formats, you can use &lt;a href="https://batchset.com/tools/image-converter" rel="noopener noreferrer"&gt;BatchSet's Image Converter&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webperf</category>
      <category>webdev</category>
      <category>images</category>
      <category>performance</category>
    </item>
    <item>
      <title>Your Image Converter Probably Doesn't Need Your Image on a Server</title>
      <dc:creator>Muhaymin Bin Mehmood</dc:creator>
      <pubDate>Sat, 05 Sep 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/muhayminbinmehmood/your-image-converter-probably-doesnt-need-your-image-on-a-server-4415</link>
      <guid>https://dev.to/muhayminbinmehmood/your-image-converter-probably-doesnt-need-your-image-on-a-server-4415</guid>
      <description>&lt;p&gt;Most online file tools follow the same architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Choose file
   ↓
Upload file
   ↓
Server processes it
   ↓
Server stores a temporary result
   ↓
Download result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That architecture is valid.&lt;/p&gt;

&lt;p&gt;But for many image operations, it is not always necessary.&lt;/p&gt;

&lt;p&gt;Modern browsers can decode, transform, and export common image formats locally. That means some tools can work without sending the original image to a backend at all.&lt;/p&gt;

&lt;p&gt;This changes the architecture in an interesting way.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Choose file
   ↓
Browser reads it locally
   ↓
Browser transforms it
   ↓
Download result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No upload step.&lt;/p&gt;

&lt;p&gt;For the right use case, that is a meaningful improvement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why client-side processing is attractive
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Privacy
&lt;/h3&gt;

&lt;p&gt;If the file does not need to leave the device, you remove an entire category of questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where is the file stored?&lt;/li&gt;
&lt;li&gt;For how long?&lt;/li&gt;
&lt;li&gt;Is it logged?&lt;/li&gt;
&lt;li&gt;Is it copied to object storage?&lt;/li&gt;
&lt;li&gt;Is a third-party processor involved?&lt;/li&gt;
&lt;li&gt;Is cleanup actually happening?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Client-side processing does not magically make an application secure, but it can reduce the amount of sensitive data that needs to cross the network.&lt;/p&gt;

&lt;p&gt;For personal photos, unreleased marketing assets, client work, screenshots, and internal images, that matters.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Less upload latency
&lt;/h3&gt;

&lt;p&gt;Uploading a 2 MB file is usually fine.&lt;/p&gt;

&lt;p&gt;Uploading 100 files is a different experience.&lt;/p&gt;

&lt;p&gt;If the transformation can happen locally, the user does not need to wait for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;upload → server queue → processing → download
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;before seeing a result.&lt;/p&gt;

&lt;p&gt;Network conditions become less important.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Lower backend cost
&lt;/h3&gt;

&lt;p&gt;If your backend is doing CPU-heavy conversion for every free user, infrastructure cost scales directly with usage.&lt;/p&gt;

&lt;p&gt;Moving suitable work to the client can reduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU usage&lt;/li&gt;
&lt;li&gt;temporary storage&lt;/li&gt;
&lt;li&gt;bandwidth&lt;/li&gt;
&lt;li&gt;queue pressure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is especially interesting for free utility tools where the server cost of a single action can be larger than the revenue generated by that user.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Better offline potential
&lt;/h3&gt;

&lt;p&gt;A browser tool that does not depend on a processing API can sometimes continue working even when connectivity is limited, depending on how the app itself is delivered and cached.&lt;/p&gt;

&lt;p&gt;That is a very different user experience from a server-only tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can the browser actually do?
&lt;/h2&gt;

&lt;p&gt;For common formats, browser APIs can already handle a surprising amount.&lt;/p&gt;

&lt;p&gt;Typical building blocks include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;File&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Blob&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FileReader&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;createImageBitmap()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;OffscreenCanvas&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Web Workers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simplified flow can look like:&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="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="nx"&gt;input&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="mi"&gt;0&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;bitmap&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;createImageBitmap&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bitmap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bitmap&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="mi"&gt;0&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="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&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/webp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.82&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 is intentionally minimal, but the important idea is that the image can be decoded and re-encoded without a traditional upload-processing-download cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hard part is not the demo
&lt;/h2&gt;

&lt;p&gt;A one-file demo is easy.&lt;/p&gt;

&lt;p&gt;Production behavior is harder.&lt;/p&gt;

&lt;p&gt;You have to think about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;memory usage&lt;/li&gt;
&lt;li&gt;large dimensions&lt;/li&gt;
&lt;li&gt;EXIF orientation&lt;/li&gt;
&lt;li&gt;transparent images&lt;/li&gt;
&lt;li&gt;browser support&lt;/li&gt;
&lt;li&gt;output quality&lt;/li&gt;
&lt;li&gt;cancellation&lt;/li&gt;
&lt;li&gt;progress reporting&lt;/li&gt;
&lt;li&gt;multiple files&lt;/li&gt;
&lt;li&gt;UI responsiveness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a user drops 200 high-resolution photos into the browser and you decode everything simultaneously, you can easily destroy the tab.&lt;/p&gt;

&lt;p&gt;So "client-side" does not mean "process everything at once."&lt;/p&gt;

&lt;h2&gt;
  
  
  Controlled concurrency matters
&lt;/h2&gt;

&lt;p&gt;A safer batch architecture uses a queue.&lt;/p&gt;

&lt;p&gt;Instead of:&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;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&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;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;processImage&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you may want a controlled number of active jobs.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;200 files waiting

Worker 1 → image
Worker 2 → image
Worker 3 → image
Worker 4 → image

Complete one → take next
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The correct concurrency depends on workload and device capabilities.&lt;/p&gt;

&lt;p&gt;The goal is not maximum parallelism.&lt;/p&gt;

&lt;p&gt;The goal is &lt;strong&gt;useful throughput without making the browser unusable&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a server is still the better choice
&lt;/h2&gt;

&lt;p&gt;Client-side processing is not a religion.&lt;/p&gt;

&lt;p&gt;There are many cases where a backend is appropriate or required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unsupported codecs&lt;/li&gt;
&lt;li&gt;extremely large files&lt;/li&gt;
&lt;li&gt;expensive AI models&lt;/li&gt;
&lt;li&gt;server-side persistence&lt;/li&gt;
&lt;li&gt;shared team workflows&lt;/li&gt;
&lt;li&gt;centralized processing rules&lt;/li&gt;
&lt;li&gt;conversions that require native libraries&lt;/li&gt;
&lt;li&gt;jobs that must continue after the tab closes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture should follow the job.&lt;/p&gt;

&lt;h2&gt;
  
  
  A product-design consequence
&lt;/h2&gt;

&lt;p&gt;Once basic image conversion can happen locally, the product can make a stronger promise:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Your common image conversions do not need to be uploaded for processing."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That was one of the design goals behind the basic image workflows I am building in &lt;a href="https://batchset.com/" rel="noopener noreferrer"&gt;BatchSet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can try the &lt;a href="https://batchset.com/tools/image-converter" rel="noopener noreferrer"&gt;Image Converter&lt;/a&gt; or the &lt;a href="https://batchset.com/tools/bulk-image-converter" rel="noopener noreferrer"&gt;Bulk Image Converter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I am mentioning it because it is the real product where I have been exploring this architecture, not because every transformation inside every application should be client-side.&lt;/p&gt;

&lt;h2&gt;
  
  
  The broader lesson
&lt;/h2&gt;

&lt;p&gt;Before adding another API endpoint, queue, storage bucket, and cleanup job, ask one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does the server actually need this file?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sometimes the answer is yes.&lt;/p&gt;

&lt;p&gt;Sometimes the browser is already powerful enough to do the job closer to the user.&lt;/p&gt;

&lt;p&gt;And when the answer is "the browser can do it," you may get privacy, speed, and infrastructure benefits at the same time.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>privacy</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Your Shopify Store Is Fast Until the Product Images Arrive: A Practical WebP Workflow</title>
      <dc:creator>Muhaymin Bin Mehmood</dc:creator>
      <pubDate>Fri, 04 Sep 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/muhayminbinmehmood/your-shopify-store-is-fast-until-the-product-images-arrive-a-practical-webp-workflow-4koc</link>
      <guid>https://dev.to/muhayminbinmehmood/your-shopify-store-is-fast-until-the-product-images-arrive-a-practical-webp-workflow-4koc</guid>
      <description>&lt;p&gt;A storefront can feel fast during development and suddenly become sluggish the moment the real product catalog is added.&lt;/p&gt;

&lt;p&gt;The code may be fine.&lt;/p&gt;

&lt;p&gt;The theme may be fine.&lt;/p&gt;

&lt;p&gt;The server may be fine.&lt;/p&gt;

&lt;p&gt;The problem is often much simpler: &lt;strong&gt;the browser is downloading far more image data than the page actually needs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you manage Shopify, WooCommerce, Daraz, or any image-heavy catalog, image optimization is not just a "convert JPG to WebP" task. It is a repeatable workflow.&lt;/p&gt;

&lt;p&gt;This is the workflow I use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Start with display size, not file format
&lt;/h2&gt;

&lt;p&gt;Imagine a product grid where every card displays an image at roughly 500px wide.&lt;/p&gt;

&lt;p&gt;Now imagine the source files are 4000px wide.&lt;/p&gt;

&lt;p&gt;Even if those 4000px images are compressed, the browser is still receiving far more image data than necessary.&lt;/p&gt;

&lt;p&gt;Before changing formats, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What size is the image actually displayed at?&lt;/li&gt;
&lt;li&gt;Do I need a large zoom image?&lt;/li&gt;
&lt;li&gt;Is the same source used for thumbnails and product detail pages?&lt;/li&gt;
&lt;li&gt;Can I generate more appropriate variants?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best optimization is often &lt;strong&gt;not shipping unnecessary pixels in the first place&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Choose a modern output format
&lt;/h2&gt;

&lt;p&gt;For many web storefronts, WebP is a strong default because it can significantly reduce file size while keeping visual quality suitable for product photography.&lt;/p&gt;

&lt;p&gt;That does not mean WebP wins every situation.&lt;/p&gt;

&lt;p&gt;PNG can still make sense for certain graphics and transparency-heavy assets.&lt;/p&gt;

&lt;p&gt;JPEG is still widely compatible.&lt;/p&gt;

&lt;p&gt;AVIF can achieve excellent compression, but your workflow, tooling, and quality requirements should decide whether the additional complexity is worth it.&lt;/p&gt;

&lt;p&gt;The practical rule is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Test your real catalog images instead of choosing a format from a benchmark headline.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Take a sample of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bright product photos&lt;/li&gt;
&lt;li&gt;dark product photos&lt;/li&gt;
&lt;li&gt;images with text&lt;/li&gt;
&lt;li&gt;transparent assets&lt;/li&gt;
&lt;li&gt;highly detailed products&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then compare visual quality and output size.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Stop converting one file at a time
&lt;/h2&gt;

&lt;p&gt;This is where image optimization turns into an operational problem.&lt;/p&gt;

&lt;p&gt;Converting 3 files manually is easy.&lt;/p&gt;

&lt;p&gt;Converting 800 product images manually is not.&lt;/p&gt;

&lt;p&gt;If every file needs the same transformation, the settings belong at the &lt;strong&gt;batch level&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A useful batch process looks 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;Select product images
        ↓
Choose output format
        ↓
Set shared quality / resize rules
        ↓
Process the batch
        ↓
Download the results together
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is much better than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Open file
Convert
Download
Rename
Repeat 799 times
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I built a browser-based version of this workflow into &lt;a href="https://batchset.com/tools/bulk-image-converter" rel="noopener noreferrer"&gt;BatchSet's Bulk Image Converter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The goal is not to make a single conversion impressive.&lt;/p&gt;

&lt;p&gt;The goal is to remove the repeated work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Preserve naming discipline
&lt;/h2&gt;

&lt;p&gt;Optimization can create a second problem: chaotic filenames.&lt;/p&gt;

&lt;p&gt;If your original files are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sku-1001-front.jpg
sku-1001-side.jpg
sku-1002-front.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;your optimized versions should remain predictable.&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 plaintext"&gt;&lt;code&gt;sku-1001-front.webp
sku-1001-side.webp
sku-1002-front.webp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avoid workflows that turn a clean catalog into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;download-1.webp
download-2.webp
final-final-2.webp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters later when you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;update products&lt;/li&gt;
&lt;li&gt;debug missing images&lt;/li&gt;
&lt;li&gt;match assets to SKUs&lt;/li&gt;
&lt;li&gt;build import spreadsheets&lt;/li&gt;
&lt;li&gt;hand the catalog to another team member&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;File naming is part of the image pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Do not optimize blindly
&lt;/h2&gt;

&lt;p&gt;A smaller file is not automatically a better file.&lt;/p&gt;

&lt;p&gt;Compression can damage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;small text&lt;/li&gt;
&lt;li&gt;fabric detail&lt;/li&gt;
&lt;li&gt;jewelry edges&lt;/li&gt;
&lt;li&gt;product labels&lt;/li&gt;
&lt;li&gt;fine textures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For e-commerce, image quality affects trust.&lt;/p&gt;

&lt;p&gt;The target is not "smallest possible file."&lt;/p&gt;

&lt;p&gt;The target is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;small enough to load quickly while still looking good enough to sell the product.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is why I prefer a sample-based process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick representative images.&lt;/li&gt;
&lt;li&gt;Test a few quality levels.&lt;/li&gt;
&lt;li&gt;Inspect them at real storefront size.&lt;/li&gt;
&lt;li&gt;Choose the lowest acceptable setting.&lt;/li&gt;
&lt;li&gt;Apply it to the full batch.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 6: Think beyond conversion
&lt;/h2&gt;

&lt;p&gt;A complete product-image workflow may include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;resizing&lt;/li&gt;
&lt;li&gt;cropping&lt;/li&gt;
&lt;li&gt;background cleanup&lt;/li&gt;
&lt;li&gt;format conversion&lt;/li&gt;
&lt;li&gt;compression&lt;/li&gt;
&lt;li&gt;naming&lt;/li&gt;
&lt;li&gt;bulk export&lt;/li&gt;
&lt;li&gt;marketplace-specific dimensions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why I think "image converter" is often too narrow a way to describe the real problem.&lt;/p&gt;

&lt;p&gt;The real problem is &lt;strong&gt;catalog preparation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your daily work involves Shopify, Daraz, Amazon, or WooCommerce product images, I put the e-commerce-focused BatchSet tools together here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://batchset.com/for/ecommerce" rel="noopener noreferrer"&gt;BatchSet for E-Commerce&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple checklist
&lt;/h2&gt;

&lt;p&gt;Before uploading a new product catalog, I would check:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Images are not massively larger than their display size&lt;/li&gt;
&lt;li&gt;[ ] Output format makes sense for the asset&lt;/li&gt;
&lt;li&gt;[ ] Compression has been visually tested&lt;/li&gt;
&lt;li&gt;[ ] Filenames remain predictable&lt;/li&gt;
&lt;li&gt;[ ] Similar files are processed as a batch&lt;/li&gt;
&lt;li&gt;[ ] Important transparent assets remain transparent&lt;/li&gt;
&lt;li&gt;[ ] Product details are still sharp enough to inspect&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Image optimization is usually presented as a format choice.&lt;/p&gt;

&lt;p&gt;It is really a &lt;strong&gt;workflow design problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you optimize one image perfectly but the process is too slow to repeat across the other 2,000 files in your catalog, the solution does not scale.&lt;/p&gt;

&lt;p&gt;A good workflow should be fast enough that optimization becomes part of publishing, not a special cleanup project you postpone for six months.&lt;/p&gt;

&lt;p&gt;If you want to try the workflow I am building around this problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://batchset.com/tools/bulk-image-converter" rel="noopener noreferrer"&gt;Bulk Image Converter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://batchset.com/for/ecommerce" rel="noopener noreferrer"&gt;BatchSet for E-Commerce&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am building BatchSet, so feedback from people managing real product catalogs is especially useful.&lt;/p&gt;

</description>
      <category>ecommerce</category>
      <category>webperf</category>
      <category>webdev</category>
      <category>webp</category>
    </item>
    <item>
      <title>Why does bulk image conversion break with remote URLs? CORS.</title>
      <dc:creator>Muhaymin Bin Mehmood</dc:creator>
      <pubDate>Thu, 03 Sep 2026 13:59:55 +0000</pubDate>
      <link>https://dev.to/muhayminbinmehmood/why-does-bulk-image-conversion-break-with-remote-urls-cors-i88</link>
      <guid>https://dev.to/muhayminbinmehmood/why-does-bulk-image-conversion-break-with-remote-urls-cors-i88</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/muhayminbinmehmood/bulk-converting-image-urls-from-csv-why-cors-breaks-the-browser-only-approach-3kcp" class="crayons-story__hidden-navigation-link"&gt;Bulk-Converting Image URLs from CSV: Why CORS Breaks the Browser-Only Approach&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/muhayminbinmehmood" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F862113%2F6e7ab150-0bd1-48ee-93a6-02227f19748c.webp" alt="muhayminbinmehmood profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/muhayminbinmehmood" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Muhaymin Bin Mehmood
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Muhaymin Bin Mehmood
                
                
              
              &lt;div id="story-author-preview-content-4505600" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/muhayminbinmehmood" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F862113%2F6e7ab150-0bd1-48ee-93a6-02227f19748c.webp" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Muhaymin Bin Mehmood&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/muhayminbinmehmood/bulk-converting-image-urls-from-csv-why-cors-breaks-the-browser-only-approach-3kcp" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 1&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/muhayminbinmehmood/bulk-converting-image-urls-from-csv-why-cors-breaks-the-browser-only-approach-3kcp" id="article-link-4505600"&gt;
          Bulk-Converting Image URLs from CSV: Why CORS Breaks the Browser-Only Approach
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/javascript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;javascript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ecommerce"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ecommerce&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/architecture"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;architecture&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/muhayminbinmehmood/bulk-converting-image-urls-from-csv-why-cors-breaks-the-browser-only-approach-3kcp" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;6&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/muhayminbinmehmood/bulk-converting-image-urls-from-csv-why-cors-breaks-the-browser-only-approach-3kcp#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>api</category>
      <category>javascript</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Bulk-Converting Image URLs from CSV: Why CORS Breaks the Browser-Only Approach</title>
      <dc:creator>Muhaymin Bin Mehmood</dc:creator>
      <pubDate>Tue, 01 Sep 2026 14:00:00 +0000</pubDate>
      <link>https://dev.to/muhayminbinmehmood/bulk-converting-image-urls-from-csv-why-cors-breaks-the-browser-only-approach-3kcp</link>
      <guid>https://dev.to/muhayminbinmehmood/bulk-converting-image-urls-from-csv-why-cors-breaks-the-browser-only-approach-3kcp</guid>
      <description>&lt;p&gt;I like browser-only image processing.&lt;/p&gt;

&lt;p&gt;For local files, the architecture can be beautifully simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user's disk
    ↓
browser
    ↓
decode
    ↓
convert
    ↓
download
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No upload.&lt;/p&gt;

&lt;p&gt;No temporary server storage.&lt;/p&gt;

&lt;p&gt;No queue worker.&lt;/p&gt;

&lt;p&gt;Then someone asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can I upload a CSV containing 500 product image URLs and convert all of those too?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At first, it sounds like the same feature with a different input source.&lt;/p&gt;

&lt;p&gt;It isn't.&lt;/p&gt;

&lt;p&gt;The moment the images live on arbitrary remote domains, your browser runs into the web's cross-origin security model.&lt;/p&gt;




&lt;h2&gt;
  
  
  Local files and remote URLs are different trust boundaries
&lt;/h2&gt;

&lt;p&gt;With a local file input:&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;input&lt;/span&gt;
  &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt;
  &lt;span class="na"&gt;accept=&lt;/span&gt;&lt;span class="s"&gt;"image/*"&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;the user explicitly gives your page access to selected files.&lt;/p&gt;

&lt;p&gt;You can create object URLs:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createObjectURL&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;decode the asset, draw it, and process it.&lt;/p&gt;

&lt;p&gt;With a remote URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://merchant-cdn.example/product-123.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;your page does not automatically have permission to fetch and read that resource however it wants.&lt;/p&gt;

&lt;p&gt;That is where CORS enters the architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  What happens with &lt;code&gt;fetch()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Suppose a CSV contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sku,image_url
A-100,https://cdn-a.example/a.jpg
A-101,https://cdn-b.example/b.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your first attempt might be:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageUrl&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;blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works only when the target server's cross-origin policy allows the request.&lt;/p&gt;

&lt;p&gt;If the server does not send suitable CORS headers, the browser blocks JavaScript from reading the response.&lt;/p&gt;

&lt;p&gt;That is intentional.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why browsers enforce this
&lt;/h2&gt;

&lt;p&gt;Without same-origin restrictions, any random site you visit could potentially try to read resources from other services where your browser has credentials or access.&lt;/p&gt;

&lt;p&gt;The same-origin policy is one of the web's fundamental security boundaries.&lt;/p&gt;

&lt;p&gt;CORS is a controlled mechanism that lets a server say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I explicitly allow this other origin to access my resource.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the image server does not grant that permission, your frontend cannot simply override it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Canvas version of the problem
&lt;/h2&gt;

&lt;p&gt;There is another variation.&lt;/p&gt;

&lt;p&gt;Sometimes an external image can visually appear in a page, but drawing it to Canvas and attempting to read/export the Canvas causes a security error.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;remote image
    ↓
Canvas draws it
    ↓
Canvas becomes tainted
    ↓
toBlob / toDataURL blocked
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MDN explains this in:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/How_to/CORS_enabled_image" rel="noopener noreferrer"&gt;Use cross-origin images in a canvas&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This matters for image converters because export normally requires access to the resulting pixel data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why &lt;code&gt;mode: "no-cors"&lt;/code&gt; does not solve it
&lt;/h2&gt;

&lt;p&gt;A common Stack Overflow-style suggestion is:&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;no-cors&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;That does not give you a readable image response.&lt;/p&gt;

&lt;p&gt;It generally produces an opaque response that JavaScript cannot inspect like a normal cross-origin resource.&lt;/p&gt;

&lt;p&gt;So this does not magically turn:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CORS denied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;full image bytes available
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your application needs the bytes, the remote server must allow the request or you need another architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture 1: fully browser-side when sources support CORS
&lt;/h2&gt;

&lt;p&gt;The ideal case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CSV
 ↓
browser parses rows
 ↓
fetch image URL
 ↓
remote CDN allows CORS
 ↓
Blob
 ↓
decode / convert
 ↓
download
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is excellent when you control the source CDN.&lt;/p&gt;

&lt;p&gt;For example, your own infrastructure can return:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Access-Control-Allow-Origin: https://yourapp.example
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or another appropriate policy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture 2: server-side fetch layer
&lt;/h2&gt;

&lt;p&gt;For arbitrary public product-image URLs, you cannot assume every server has a browser-friendly CORS policy.&lt;/p&gt;

&lt;p&gt;A common architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CSV
 ↓
browser parses / previews
 ↓
send URL job
 ↓
your backend fetches remote image
 ↓
validate response
 ↓
convert / normalize
 ↓
return output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server is not restricted by browser CORS in the same way because CORS is primarily a browser-enforced policy.&lt;/p&gt;

&lt;p&gt;But now you have new security problems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Server-side URL fetching creates SSRF risk
&lt;/h2&gt;

&lt;p&gt;Do not build:&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="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/fetch-image&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;arrayBuffer&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;and call it finished.&lt;/p&gt;

&lt;p&gt;If users can make your server fetch arbitrary URLs, you may accidentally create a Server-Side Request Forgery surface.&lt;/p&gt;

&lt;p&gt;An attacker could try:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:...
http://127.0.0.1:...
http://169.254.169.254/...
private network hosts
redirect chains
unexpected protocols
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A production fetcher needs controls.&lt;/p&gt;




&lt;h2&gt;
  
  
  A safer remote-fetch checklist
&lt;/h2&gt;

&lt;p&gt;At minimum, consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;allow http/https only
resolve DNS carefully
block private/internal IP ranges
limit redirects
re-validate every redirect target
limit response size
limit time
validate content type
validate actual file signature
rate limit users
avoid forwarding user cookies
avoid forwarding internal credentials
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Proxy the URL through the backend" sounds easy.&lt;/p&gt;

&lt;p&gt;A secure fetch service is not trivial.&lt;/p&gt;




&lt;h2&gt;
  
  
  Validate actual image content
&lt;/h2&gt;

&lt;p&gt;Do not trust only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Content-Type: image/jpeg
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server may be wrong or malicious.&lt;/p&gt;

&lt;p&gt;You should enforce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;maximum bytes&lt;/li&gt;
&lt;li&gt;expected media types&lt;/li&gt;
&lt;li&gt;decodable image content&lt;/li&gt;
&lt;li&gt;maximum dimensions&lt;/li&gt;
&lt;li&gt;reasonable processing limits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A file that claims to be an image can still be dangerous from a resource-exhaustion perspective.&lt;/p&gt;




&lt;h2&gt;
  
  
  Parse the spreadsheet in the browser first
&lt;/h2&gt;

&lt;p&gt;Even when remote fetching requires a backend, you can still keep useful work client-side.&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 plaintext"&gt;&lt;code&gt;Excel / CSV
     ↓
browser parses file
     ↓
show columns
     ↓
detect image URL column
     ↓
validate obvious row errors
     ↓
preview first rows
     ↓
user confirms job
     ↓
server processes URLs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is better UX than immediately uploading the spreadsheet and making the user guess what will happen.&lt;/p&gt;

&lt;p&gt;It also reduces unnecessary server jobs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example CSV workflow
&lt;/h2&gt;

&lt;p&gt;Suppose we support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sku,image_url,format,quality
SKU-001,https://cdn.example/1.jpg,webp,82
SKU-002,https://cdn.example/2.png,webp,82
SKU-003,https://other.example/3.jpg,jpg,90
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser can validate:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&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;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="k"&gt;try&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;url&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;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image_url&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http:&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;https:&lt;/span&gt;&lt;span class="dl"&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;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;protocol&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="nx"&gt;errors&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unsupported URL protocol&lt;/span&gt;&lt;span class="dl"&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;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;errors&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid image URL&lt;/span&gt;&lt;span class="dl"&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="o"&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;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;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;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;format&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="nx"&gt;errors&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unsupported format&lt;/span&gt;&lt;span class="dl"&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;errors&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;That catches obvious bad data before the remote job starts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Queueing matters at 500 URLs
&lt;/h2&gt;

&lt;p&gt;Do not launch:&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;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;processUrl&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;against 500 large images unless your system is explicitly designed for that concurrency.&lt;/p&gt;

&lt;p&gt;A queue gives you control.&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;runPool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;concurrency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="p"&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;index&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;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;while &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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="o"&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;current&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;
        &lt;span class="nx"&gt;items&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="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;}&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;current&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="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;concurrency&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;run&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;Now you can tune:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;network concurrency
CPU conversion concurrency
memory usage
provider rate limits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;separately if needed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Failure reporting is part of the feature
&lt;/h2&gt;

&lt;p&gt;In a batch of 500 URLs, some will fail.&lt;/p&gt;

&lt;p&gt;Reasons include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;404&lt;/li&gt;
&lt;li&gt;timeout&lt;/li&gt;
&lt;li&gt;unsupported format&lt;/li&gt;
&lt;li&gt;CORS&lt;/li&gt;
&lt;li&gt;server rejection&lt;/li&gt;
&lt;li&gt;image too large&lt;/li&gt;
&lt;li&gt;corrupt file&lt;/li&gt;
&lt;li&gt;DNS failure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not make the entire job fail because row 317 is bad.&lt;/p&gt;

&lt;p&gt;Return a result report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sku,status,error
SKU-001,success,
SKU-002,success,
SKU-003,failed,HTTP 404
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user needs to know what to fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the browser-only rule should not become ideology
&lt;/h2&gt;

&lt;p&gt;"Everything should happen locally" is a useful design preference.&lt;/p&gt;

&lt;p&gt;It should not become a fake claim when the problem no longer fits.&lt;/p&gt;

&lt;p&gt;For local files, browser-only conversion is a great architecture.&lt;/p&gt;

&lt;p&gt;For arbitrary remote URLs, the browser's security boundary changes the problem.&lt;/p&gt;

&lt;p&gt;The correct design can be hybrid:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local files:
browser

Spreadsheet parsing:
browser

Remote URL fetch:
server when required

Preview:
browser

Final download:
browser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not a failure of client-side architecture.&lt;/p&gt;

&lt;p&gt;It is a consequence of the web security model.&lt;/p&gt;




&lt;h2&gt;
  
  
  This is exactly why my Excel/CSV workflow differs from normal BatchSet conversion
&lt;/h2&gt;

&lt;p&gt;BatchSet's ordinary local-file conversion can process files in the browser.&lt;/p&gt;

&lt;p&gt;But the Excel/CSV Image Converter has to deal with remote URLs that may live on arbitrary CDNs.&lt;/p&gt;

&lt;p&gt;So the workflow is intentionally different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;spreadsheet
→ preview
→ account-backed remote batch
→ fetch images
→ convert
→ package output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The live tool explains this distinction rather than pretending remote URL ingestion is identical to local processing.&lt;/p&gt;

&lt;p&gt;If you already have a product catalog in Excel or CSV and want to test the workflow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://batchset.com/tools/excel-convert?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=devto_excel_image_urls_20260901&amp;amp;utm_content=end_cta" rel="noopener noreferrer"&gt;Open BatchSet's Excel/CSV Image Converter&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Product insight: architecture can become a marketing advantage
&lt;/h2&gt;

&lt;p&gt;Developers notice when a product explains its boundaries.&lt;/p&gt;

&lt;p&gt;Instead of saying:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100% browser-only!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for every feature, a more credible message is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local files stay local.

Remote URL jobs use a server fetch layer
when the browser cannot legally read
the remote source.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is both more accurate and more useful.&lt;/p&gt;

&lt;p&gt;Users do not need slogans.&lt;/p&gt;

&lt;p&gt;They need to know where their data goes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;Bulk image conversion from a spreadsheet is not just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;loop over URLs
→ convert
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The moment your source images live on other domains, your architecture must account for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CORS&lt;/li&gt;
&lt;li&gt;Canvas tainting&lt;/li&gt;
&lt;li&gt;safe remote fetching&lt;/li&gt;
&lt;li&gt;SSRF&lt;/li&gt;
&lt;li&gt;timeouts&lt;/li&gt;
&lt;li&gt;validation&lt;/li&gt;
&lt;li&gt;concurrency&lt;/li&gt;
&lt;li&gt;partial failures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For local files, the browser can be the entire processing engine.&lt;/p&gt;

&lt;p&gt;For arbitrary remote URLs, a hybrid client/server pipeline is often the more honest and robust design.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>ecommerce</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
