Skip to content
Boomspot
  • Home
Loading...
Boomspot

Daily tech news, software development coverage, Apple reporting, and the gear behind modern music making.

TwitterLinkedIn

Browse

  • Categories
  • Tags
  • Authors

Company

  • About
  • Contact

Legal

  • Privacy Policy
  • Terms of Service
  • Unsubscribe

© 2026 Boomspot. All rights reserved.

Built by Boomspot
Updated hourly

AI Content Disclosure: Articles on Boomspot are researched, written, and edited with the assistance of advanced AI systems. We combine software-assisted research with editorial oversight to deliver useful, accurate, and practical technical and music production content. Learn more about our editorial approach.

Browse by Category

Technology663Coding173Linux49SEO48Music Production31Studio Gear29Apple Rumors11

Popular Posts

Raspberry Pi 5 Alternatives After the $77.50 Price Hike

Raspberry Pi 5 Alternatives After the $77.50 Price Hike

6 min read
GamesSoundCon 2026: Worth It for Composers?

GamesSoundCon 2026: Worth It for Composers?

6 min read
Why External Hard Drive Prices Are Skyrocketing in 2026

Why External Hard Drive Prices Are Skyrocketing in 2026

5 min read
Which Ethernet Cable Category Do You Actually Need?

Which Ethernet Cable Category Do You Actually Need?

6 min read
ROCm or Vulkan for llama.cpp on AMD GPUs? How to Decide

ROCm or Vulkan for llama.cpp on AMD GPUs? How to Decide

8 min read

Recent Posts

Is Plugin Boutique Gold Tier Worth It? Spend Math Guide

Is Plugin Boutique Gold Tier Worth It? Spend Math Guide

Oct 22, 2026•7 min
How to Draft Customer Support Emails with Ollama Locally

How to Draft Customer Support Emails with Ollama Locally

Oct 22, 2026•8 min
Validate CSV Upload Encoding in JavaScript, Step by Step

Validate CSV Upload Encoding in JavaScript, Step by Step

Oct 22, 2026•8 min
How to Choose B2B Marketing Channels: A Scoring Model

How to Choose B2B Marketing Channels: A Scoring Model

Oct 22, 2026•9 min
JUPITER-08 vs n/vision: Which Roland Boutique to Buy?

JUPITER-08 vs n/vision: Which Roland Boutique to Buy?

Oct 22, 2026•6 min
  1. Home
  2. Coding
  3. Convert HEIC to WebP with Node.js: Batch Script Guide
coding9 min read

Convert HEIC to WebP with Node.js: Batch Script Guide

sharp often cannot decode HEIC out of the box. Here is a Node.js pipeline that decodes with heic-convert, writes WebP and JPG, verifies the batch and prints picture tags.

S

Staff

October 22, 2026

Reviewed byDorian

Convert HEIC to WebP with Node.js: Batch Script Guide

Your client sends 40 iPhone photos. You drag them into a GUI converter, export one by one, and the third batch comes out at a different quality than the first. Meanwhile, the obvious scripted fix, sharp, may throw an error the moment it sees a .heic file.

This guide builds a repeatable pipeline instead: decode HEIC, encode WebP plus a JPG fallback, check the results, and print <picture> markup. Every code block here is unexecuted. Nothing below was run and no real output is pasted, so treat the console output as an illustration of the format, not a measurement.

What you will do

  1. Install a HEIC decoder and an encoder, and pin their versions.
  2. Run one script that converts a folder of HEIC files.
  3. Read the built-in verification report.
  4. Fix the HEIC decode error if sharp rejects your files.
  5. Paste the generated <picture> markup and finish the pre-publish checklist.

Why convert at all (and what is unverified)

A source article on converting HEIC for websites claims that most browsers cannot display HEIC and that WebP files run 25 to 34 percent smaller than JPG at the same quality, and that faster pages rank better (source). Treat all three as unverified.

The browser claim needs precision. Safari can display HEIC on Apple platforms, while support elsewhere is not something you should rely on. Check a current compatibility table before deciding. The size claim depends on your photos and encoder settings, which is exactly why the script below prints WebP and JPG sizes side by side. The ranking claim is a general assertion with no evidence in the source, so this guide does not lean on it.

The safe position is simple: HEIC is a poor delivery format for a public site, and a WebP file with a JPG fallback works across a wide range of browsers. The same source notes that very old browsers, including Internet Explorer and pre-2020 Safari, lack WebP support, which is what the JPG fallback covers.

Step 1: Pick your libraries and pin versions

You have a few ways to decode HEIC in Node.js, each with trade-offs. The following comparison is an inference from how these tools are built, so confirm against each project's documentation.

sharp alone is the fastest and simplest option, but its prebuilt binaries often cannot decode HEIC (see the failure section). heic-convert decodes in JavaScript and WebAssembly, so it installs anywhere, though it is slower and uses more memory per image. A custom libvips build with an HEVC-capable libheif restores direct sharp decoding, at the cost of a native build you must maintain and possible codec licensing questions. The ImageMagick command line is another route if its HEIC delegate is installed, but it adds a system dependency.

This guide uses heic-convert to decode and sharp to encode. You want the fewest native surprises on a client's machine.

Node.js version: the script uses ES modules and top-level await, so it assumes a currently supported Node.js release (18 or newer is a safe assumption). Check the sharp installation documentation for its exact minimum. This article does not name package versions because none were verified. Pin whatever resolves on your machine:

mkdir heic-pipeline && cd heic-pipeline
npm init -y
npm pkg set type=module
npm install --save-exact heic-convert sharp
node --version
npm ls --depth=0

Commit package.json and the lockfile, and paste the node --version and npm ls output into your project README. That is your version record.

Step 2: Run the conversion script (unexecuted)

Put your HEIC files in ./originals and keep that folder out of your build output. Save this as convert.mjs:

// convert.mjs (UNEXECUTED example)
import { readdir, readFile, writeFile, mkdir, stat } from 'node:fs/promises';
import path from 'node:path';
import convert from 'heic-convert';
import sharp from 'sharp';

const [inDir = './originals', outDir = './public/img'] = process.argv.slice(2);
await mkdir(outDir, { recursive: true });

const files = (await readdir(inDir)).filter((f) => /\.heic$/i.test(f));
if (files.length === 0) {
  console.error('No .heic files found in ' + inDir);
  process.exit(1);
}

const snippets = [];
let problems = 0;

for (const file of files) {
  const name = path.parse(file).name;
  const webpPath = path.join(outDir, name + '.webp');
  const jpgPath = path.join(outDir, name + '.jpg');

  // 1. Decode HEIC to a lossless PNG buffer so only one lossy step follows.
  const png = Buffer.from(
    await convert({ buffer: await readFile(path.join(inDir, file)), format: 'PNG' })
  ); For more on this, see [more on filesystem mcp server in claude desktop: safe setup guide](/filesystem-mcp-server-in-claude-desktop-safe-setup-guide).

  // 2. Encode the delivery files. Sequential on purpose: decoding is memory-heavy.
  await sharp(png).webp({ quality: 80 }).toFile(webpPath);
  await sharp(png).jpeg({ quality: 82, mozjpeg: true }).toFile(jpgPath);

  // 3. Verify dimensions and sizes.
  const src = await sharp(png).metadata();
  const w = await sharp(webpPath).metadata();
  const j = await sharp(jpgPath).metadata();
  const webpKB = (await stat(webpPath)).size / 1024;
  const jpgKB = (await stat(jpgPath)).size / 1024;

  const sameDims = [w, j].every((m) => m.width === src.width && m.height === src.height);
  const webpSmaller = webpKB < jpgKB;
  if (!sameDims || !webpSmaller) problems++;

  console.log(
    `${name} ${src.width}x${src.height} webp=${webpKB.toFixed(0)}KB jpg=${jpgKB.toFixed(0)}KB ` +
      (sameDims && webpSmaller ? 'ok' : 'CHECK')
  );

  snippets.push(
    `<picture>\n  <source srcset="/img/${name}.webp" type="image/webp">\n  <img src="/img/${name}.jpg" width="${src.width}" height="${src.height}" alt="TODO: describe this photo" loading="lazy">\n</picture>`
  );
}

await writeFile(path.join(outDir, 'picture-snippets.html'), snippets.join('\n\n') + '\n');
console.log(`${files.length} files processed, ${problems} need a look.`);

Run it with node convert.mjs ./originals ./public/img.

The quality values (80 for WebP, 82 for JPG) are starting points, not recommendations backed by testing. Adjust them after your visual check.

Expected console output (illustrative format only)

If the script works, you should see one line per photo. Filenames and numbers here are placeholders:

IMG_0001 4032x3024 webp=NNN KB jpg=NNN KB ok
IMG_0002 3024x4032 webp=NNN KB jpg=NNN KB ok
2 files processed, 0 need a look.

The PNG intermediate keeps the decode lossless, which is why the pipeline does not chain JPG to WebP. Re-encoding a lossy copy softens detail further. The same source article makes the point that every conversion step can soften fine detail, so do cropping and color correction before this script runs. See full coverage of how to check laptop linux kernel support before 7.4 for additional background.

Step 3: Read the verification report

The script already checks the two things that catch most bad batches. The first is dimensions: both outputs must match the decoded image. A portrait photo that reports a landscape size, such as 4032x3024 for a phone held upright, tells you rotation was lost. The second is size: the WebP should be smaller than the JPG of the same photo. If it is not, lower the WebP quality or inspect that image, since the source article makes the same suggestion.

A CHECK flag does not mean the file is broken. It means a person should look. Screenshots with sharp text are the usual culprit, because they compress differently from photographs.

Then do the manual part no script can do. Open the original and the WebP side by side at 100 percent zoom and look at edges, text and smooth gradients, where artifacts appear first. If you cannot tell them apart after ten seconds, visitors will not either.

Also check for leftover location data if the photos came from clients. sharp generally drops metadata unless you ask it to keep it, but confirm with a tool such as exiftool on one output file rather than assuming.

Step 4: Fix the HEIC decode error in sharp

You may be tempted to skip heic-convert and write this:

// UNEXECUTED, and likely to fail with prebuilt binaries
await sharp('IMG_0001.HEIC').webp().toFile('out.webp');

With many prebuilt sharp installs, this throws an error along these lines (wording varies by version, and no real output was captured here):

Error: Input file contains unsupported image format

Also read: how to compare software development quotes like for like

Some builds report a heif plugin or unsupported codec message instead.

The cause, to the best of current understanding: iPhone HEIC photos typically use the HEVC codec, and the libvips bundled in sharp's prebuilt binaries ships without an HEVC decoder, largely because of codec licensing. Check the sharp documentation for the current status, since this can change between releases.

The fix is the two-stage approach above: decode with heic-convert, then hand sharp a PNG buffer it understands. The alternative is installing a global libvips built with an HEVC-capable libheif and building sharp against it. That is faster per image but harder to reproduce on every teammate's machine.

If a big batch crashes with a memory error, that is a likely side effect of decoding in WebAssembly. This is an inference. The script already processes files one at a time, so split the folder into smaller batches before trying anything else.

Step 5: Publish with a fallback and archive the originals

The script writes ready-to-paste markup to public/img/picture-snippets.html. Each entry looks like this:

<picture>
  <source srcset="/img/IMG_0001.webp" type="image/webp">
  <img src="/img/IMG_0001.jpg" width="4032" height="3024" alt="TODO: describe this photo" loading="lazy">
</picture>

Browsers that understand WebP load the first source. Everything else falls back to the JPG. The width and height attributes let the browser reserve space before the image loads. For a full-size 4032-pixel original on a web page, consider resizing in sharp too, but verify that your dimension check still passes.

Pre-publish checklist

  1. Search the snippets for TODO and replace every placeholder with real alt text.
  2. Confirm no CHECK lines remain, or that you inspected each one.
  3. Spot-check at least three outputs at 100 percent zoom against the originals.
  4. Archive the original HEIC files somewhere outside the web root. WebP is a delivery format, so re-edit from the originals, never from a compressed copy.
  5. Keep the JPG fallbacks next to the WebP files.
  6. Start with the images visitors actually download, such as hero images and anything over 200 KB, as the source article advises, rather than converting a whole library.

A no-code option

If you only have a handful of photos and no appetite for a script, an in-browser converter works. Disclosure: FileOnTap is built by the author of the article this guide draws on. That article says it converts locally with no upload, accepts up to 10 files per batch, and uses a fixed quality setting with no slider (source). Those are the developer's claims and have not been verified here. You also would not get the dimension and size checks or the generated markup.

What to expect next

On your first real batch, expect to tune the two quality numbers once and then leave them alone. Expect screenshots and graphics to be the images that need individual treatment. From there, the natural extension is a resize step, or wiring convert.mjs into your build so new uploads convert automatically, using the same decode-then-encode order.

Tags

Software DevelopmentCoding TutorialsDeveloper ToolsWeb DevelopmentOpen Source

Keep reading

Your Guide to GitHub Universe 2025: Schedule Launched!
Coding•3 min read

Your Guide to GitHub Universe 2025: Schedule Launched!

Get ready for GitHub Universe 2025! Check out the schedule, create your personalized agenda, and sign up for mentoring sessions. Join us for an exciting experience!

Sep 13, 2025

Unlocking ChatGPT Developer Mode: Full MCP Client Access
Coding•4 min read

Unlocking ChatGPT Developer Mode: Full MCP Client Access

Unlock the power of ChatGPT Developer Mode with full MCP client access. Discover how to enhance your coding projects and streamline development.

Sep 11, 2025

Stack Overflow's New Learning Resources for Coders
Coding•3 min read

Stack Overflow's New Learning Resources for Coders

Explore the latest learning tools from Stack Overflow designed to empower coders with hands-on exercises, expanded topics, and practical insights.

Sep 10, 2025

More stories for your next project

Get tech, coding, and music production updates in your inbox.

Unsubscribe anytime.