DEV Community

Daniel Pertu
Daniel Pertu

Posted on

Our release doc says 600 MB. The download is 278, and the app's own index lists 355 MB that is not there.

Notifio is a desktop app that watches rental search pages and emails you when a new listing appears. It is Electron, it ships its own copy of Chromium so there is nothing to install afterwards, and its RELEASES.md opens with this table:

| File                | Platform                   | Size   |
| notifio-arm64.dmg   | macOS Apple Silicon        | ~600MB |
| notifio-x64.dmg     | macOS Intel                | ~600MB |
| notifio-setup.exe   | Windows 10+ 64-bit         | ~300MB |
Enter fullscreen mode Exit fullscreen mode

Followed by a breakdown of "what's inside the ~600MB": about 350 MB of Electron, about 180 MB of Chromium, about 70 MB of everything else.

All six of those numbers are wrong as file sizes, and two of them are roughly right about something else.

Measuring a download you do not want to download

The download buttons hit our own endpoint, which answers with a 302 to a presigned R2 URL that stops working 60 seconds later. A HEAD to that URL is refused:

HTTP/1.1 403 Forbidden
Enter fullscreen mode Exit fullscreen mode

Which is correct behaviour, not a bug: a SigV4 signature covers the HTTP method, and this one was signed for GetObject. The consequence is that no link checker, no uptime monitor and no curl of mine can confirm the file is there without pulling a quarter of a gigabyte.

A ranged request can, though. One byte is a GET.

loc=$(curl -sI https://notifio.app/api/download/win | grep -i '^location:' | cut -d' ' -f2-)
curl -s -r 0-0 -o /dev/null -D - "$loc" | grep -i 'content-range\|last-modified\|etag'
Enter fullscreen mode Exit fullscreen mode
notifio-setup.exe    237,616,310 bytes   226.6 MiB   28 Jul 2026   ETag ...-29
notifio-arm64.dmg    291,511,009 bytes   278.0 MiB   22 Sep 2026   ETag ...-35
notifio-x64.dmg      301,712,899 bytes   287.7 MiB   22 Sep 2026   ETag ...-36
Enter fullscreen mode Exit fullscreen mode

The documented 600 MB downloads are 278 MB and 288 MB. The documented 300 MB Windows download is 227 MB. The dates are the other thing in there: the Windows installer is still 56 days older than the Mac ones, because nothing has needed a Windows rebuild since.

The suffix on each ETag is the multipart count. Divide and you get 8 MiB parts, which is the AWS CLI's default, which is the upload command written out three lines further down the same doc. The ETag of a file tells you which tool put it there.

Where 600 came from

It is the installed size. On disk, before compression:

Notifio.app (arm64)   645 MB
Notifio.app (x64)     656 MB
Enter fullscreen mode Exit fullscreen mode

So the table is honest about a number and has it in the wrong column. A DMG is compressed, 645 MB of mostly-binary content comes out at 278 MB, and the reader of that table is a person deciding whether to tap a button on hotel wifi. They care about the 278.

The real breakdown

claimed                        measured (arm64 bundle)
~350 MB  Electron              274 MB  Electron Framework.framework
~180 MB  Chromium              344 MB  playwright-browsers/chromium-1228
~ 70 MB  node modules etc       21 MB  app.asar
                                 4 MB  app.asar.unpacked/node_modules
Enter fullscreen mode Exit fullscreen mode

The two biggest items are the wrong way round. Chromium is not half the size of Electron, it is 70 MB bigger, because the bundled browser is a full Chrome-for-Testing build while the Electron framework is a stripped shell. Everything that is not those two is 25 MB, not 70.

And inside that, our own compiled application:

dist-ts    28 files    287 KB     compiled main process
public      3 files    250 KB     the built UI
Enter fullscreen mode Exit fullscreen mode

537 KB of product in a 278 MB download, about two tenths of one per cent. Every decision in this post is about the other 99.8 per cent, which is the ordinary condition of shipping a desktop app and still worth seeing written down once.

25 exclusion globs, 449 MB, and the wrong directory

The files array in package.json carries 25 negated globs aimed at the pnpm store, so that build tooling does not get packaged. I measured every one of them against the real directory.

295.3 MB   electron@*
 30.6 MB   electron-winstaller*
 22.5 MB   typescript*
 17.3 MB   @img*
 17.3 MB   lightningcss*
 15.6 MB   @rolldown*
 10.2 MB   esbuild*
 10.1 MB   @esbuild*
  7.0 MB   @electron-internal*
  5.3 MB   @types*          (20 directories)
  ... 15 more, 17.4 MB between them
448.6 MB   total, out of a 511.4 MB store
Enter fullscreen mode Exit fullscreen mode

88 per cent of the pnpm store is excluded, the single biggest win is excluding Electron itself from the app's own file list, and one glob matches nothing at all:

"!node_modules/.pnpm/rollup*/**/*",
Enter fullscreen mode Exit fullscreen mode

Vite 8 uses Rolldown here, so @rolldown* is doing the work and rollup* is a line that has never excluded a byte. It is harmless and I am keeping it, for the same reason the three paths in the same config that do not exist are still there: a glob that matches nothing costs nothing, and the day a dependency brings Rollup back it is already handled.

The part that is worth taking away: all 25 of those globs argue over a directory that contributes about 63 MB to the build, while the 618 MB that actually makes the app big never passes through files at all. Chromium is staged into the bundle by a script, and the Electron framework is put there by electron-builder itself. Tuning the exclusion list is the most satisfying and least significant size work available.

The index that describes a file that was deleted

An .asar archive starts with a JSON header listing every path and size. Here is what the shipped arm64 bundle's header says about the browser directory, next to what the directory holds:

header says                            on disk
342.8 MB  335 files  chrome-mac-arm64  yes
354.8 MB  335 files  chrome-mac-x64    no
697.6 MB  671 files  chromium-1228     342.8 MB
Enter fullscreen mode Exit fullscreen mode

Both architectures are in the index. Only one is in the app. That is the afterPack hook doing its job so that one DMG does not ship two Chromiums: it deletes the wrong-arch browser from the packaged .app, and it necessarily runs after the archive index was written, so the index keeps describing 335 files that are no longer there.

Nothing reads them. Playwright resolves the browser path for the architecture it is running on, finds chrome-mac-arm64, and never asks about the other one. But the lesson generalises past this app: an asar header is an index, not an inventory, and any code that treats a listing as proof of existence is relying on a file that a post-processing step is free to remove.

It also explains a measurement that confused me for ten minutes. If you sum the sizes in that header you get 722 MB, for an app bundle of 645 MB holding a 21 MB archive. The header is describing a build that existed for about four seconds.

What I am changing

The doc, obviously: real byte counts, the installed size labelled as the installed size, and the breakdown in descending order of what is actually biggest.

Then the download page, which is also our updater and currently contains the string "MB" zero times. Three buttons, no sizes, no dates. The numbers exist, the endpoint that serves the file can read them from R2 on request, and a person about to spend 278 MB is entitled to know before they click.

You can try the buttons at notifio.app/download, with the one-time licence at notifio.app/pricing, the per-site setup guides at notifio.app/alerts, and what is kept on your own machine rather than ours in section 6 of the privacy notice.

Top comments (0)