Back

Four create-electron-vite build issues and fixes

September 27, 2026 · Tech

Companion post: "create-electron-vite quickstart, with AI coding" — that one covers creating the project and hooking up AI; this one is all about the build.

Conclusion first: all four create-electron-vite build issues are independent of your framework choice — React, Vue, and Vanilla variants all hit them, because they share the same origin: the scaffold is essentially "an official Vite web template + a very thin Electron patch", and the patch itself is buggy. The good news: one unified set of fixes works for every variant, and there is a copy-paste list for your AI agent at the end.

The four issues in one line each:

  • TS6133: an unused require declaration shipped in the template — the first build always fails;
  • Electron download failure: two download moments and one cache that only recognizes flat files, none of which talk to each other — an unreliable GitHub connection kills the packaging stage;
  • Bloated asar: the entire dependencies node_modules tree gets packed into the installer — measured 282.8 MB → 34.7 MB;
  • Ghost files: nothing ever cleans dist-electron/, so stale build output accumulates and ships.

How this was verified: fresh React and Vue projects created with the scaffold and built end to end, plus one real React project (electron 35.7.5) as a control; every fix below was validated in a real build. Environment: Windows 11, Node v24.15.0, create-electron-vite 0.7.1, electron-builder ^24.13.3.

To make the root causes below land, first understand the three stages npm run build actually runs:

tsc (or vue-tsc)  →  vite build  →  electron-builder
   type-check         frontend bundle    desktop installer packaging
                      emits dist/ (renderer)   references dist-electron/ (main process)
                                        + installers under release/

What electron-builder does: it takes dist/, dist-electron/, package.json, and the entire production node_modules tree of your dependencies, plus the Electron runtime itself, and assembles them into app.asar and the installer. Remember "dependencies get packed wholesale" — it matters later.

By analogy: shipping a desktop app is moving house. tsc is the building inspector who flags anything off against the blueprint; vite build is the renovation crew that turns the bare shell (source code) into a finished apartment (dist/) with the furniture welded to the walls; electron-builder is the moving company that packs the whole apartment — car keys (the Electron runtime) included — into a shipping container (the installer). All four issues below happen on the road between renovation and move-in.

Issue 1: the very first build fails with TS6133 (yes, even React)

Symptom: npm run build dies at step one. Actual log from the React variant:

> tsc && vite build && electron-builder

electron/main.ts(6,7): error TS6133: 'require' is declared but its value is never read.

Root cause: the template's electron/main.ts contains const require = createRequire(import.meta.url), which is never used; meanwhile the scaffold unconditionally adds "electron" to the tsconfig include for every variant, and the template enables noUnusedLocals. "Unused + strictly checked" collide, so all three templates are red out of the box. We verified on the React variant that type-checking of electron/ is genuinely active (plant a type error in main.ts and tsc immediately reports TS2322) — so don't try to drop electron from include to work around it; that would forfeit type safety for your main process.

In moving-house terms: the renovation crew left a dead wire in the wall, and the inspector's rule happens to be "reject anything unused". The house is fine — it's the leftover colliding with a strict standard. The fix is not to relax the standard but to pull out the wire.

Fix: delete these two lines at the top of electron/main.ts (for every variant):

- import { createRequire } from 'node:module'
  import { fileURLToPath } from 'node:url'
  ...
- const require = createRequire(import.meta.url)
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
diff

Issue 2: build hangs on "downloading electron", fails with ERR_ELECTRON_BUILDER_CANNOT_EXECUTE

Symptom: the first two stages pass, then electron-builder reports:

⨯ Get "https://github.com/electron/electron/releases/download/v30.5.1/electron-v30.5.1-win32-x64.zip": ...
⨯ app-builder.exe process failed ERR_ELECTRON_BUILDER_CANNOT_EXECUTE

Root cause: the Electron binary has two download moments and one actual cache, and none of them interoperate.

  1. During npm install, the electron package's install script extracts the runtime into node_modules/electron/dist — which many people mistake for "already downloaded";
  2. At packaging time, electron-builder never touches node_modules/electron/dist. It looks for a zip file according to the electron version in package.json: %LOCALAPPDATA%\electron\Cache\electron-v<version>-win32-x64.zip (tested on 24.13.3: it only recognizes flat files with that exact name in this directory, without recursing into subdirectories);
  3. Not found → it downloads from GitHub Releases on the spot. If the GitHub connection is flaky (timeouts / EOF), the build dies here.

Back to the analogy: the renovation crew already brought an identical marble countertop into your home (node_modules/electron/dist), but the moving company doesn't inspect your home — it picks up "factory-sealed" stock from the warehouse by model number; and the warehouse keeper only recognizes the shelf labeled to standard, ignoring the same item sitting in an unlabeled box. "It's in my home" and "the warehouse can produce it" are two different ledgers.

The two most common mental traps:

  • "npm install succeeded — why does packaging download again?" — because the two steps use two different artifacts. The npm install download (newer @electron/get) stores the zip under %LOCALAPPDATA%\electron\Cache\<sha256 hash directory>\; electron-builder only recognizes flat file names, so it cannot see zips inside hash subdirectories. That's the "I clearly have a cache but it still fails" mystery.
  • Does a mirror download get persisted? Yes. After configuring a mirror, the first download drops the zip into that flat location, and subsequent builds pass even offline (verified).

Fix (recommended — put it in project config once and for all) — electron-builder.json5:

{
  // direct GitHub access is unreliable; route binaries through npmmirror
  "electronDownload": {
    "mirror": "https://npmmirror.com/mirrors/electron/"
  },
  "directories": { "output": "release/${version}" },
  // ...
}
json5

Offline fallback: manually place the zip — named exactly electron-v<version>-win32-x64.zip — flat inside %LOCALAPPDATA%\electron\Cache\ (e.g. copy it out of a hash subdirectory), and the next build hits it directly.

Issue 3: absurd installer size — the whole node_modules ends up in asar

Symptom: a project with barely any code has an asar of several MB; real projects can reach hundreds of MB.

Root cause: the real meaning of dependencies is not "packages my project uses" but "packages electron-builder will pack into asar". Meanwhile, when Vite bundles the renderer, it has already compiled React/Vue and all frontend libraries into dist/ — the copies in node_modules are a second set, pure dead weight.

dependencies is not your shopping list; it's the moving company's shipping list. The renovation crew already welded the furniture into the finished apartment, yet the mover ships another set of raw materials from the warehouse per the list and stuffs it all into your container.

Measured (before = template as-is, after = dependency partitioning):

ProjectTotal asarof which node_modules
Fresh React template4.9 MB4.7 MB (react tree, 5 packages)
Fresh React template, fixed0.1 MB0 (node_modules gone entirely)
Fresh Vue template14.1 MB14.0 MB (vue family, 12 packages)
Fresh Vue template, fixed0.1 MB0
Real project (React, chart libs)282.8 MB269.2 MB (mermaid alone 122 MB)
Real project, fixed34.7 MB23.6 MB

The installer slimmed down in step: 139 MB → 91.7 MB.

Fix: partition dependencies in package.json —

  • Keep in dependencies: third-party packages the main process genuinely needs at runtime. How to tell: whatever electron/main.ts (and other main-process files) imports; if you configured an external list in vite.config.ts, use that as the source of truth;
  • Move to devDependencies: everything renderer-only (frameworks like react / react-dom / vue, chart libs like recharts, icon libs like lucide, UI component libraries). devDependencies install as usual and Vite bundles them as usual — they just never enter asar.

One rule of thumb: "If I removed this package, would the packaged app still run?" If yes → devDependencies.

Issue 4: "ghost files" from old builds sneak into asar

Symptom: .js files referenced by no code (old chunks with content hashes) appear in dist-electron/ and get packed into the installer as-is. In a real project this accumulated to 7 files, ~2 MB, including two 950 KB complete old main-process bundles.

Root cause: vite build only cleans its own output directory dist/; dist-electron/ is written by vite-plugin-electron, and nobody cleans it. Any change that alters output file names (renaming a module, adding/removing dynamic imports, chunk splitting) leaves the old files behind permanently. Reproduced on the React variant: add a dynamic-import module extra.ts to the main process (emitting extra-DPF_M5d5.js), rename it extra2.ts, rebuild — the old extra-DPF_M5d5.js still sits in dist-electron/ and gets packed into asar.

Only one room in this house ever gets cleaned (dist/ — the renovation crew tidies it every time); in the dist-electron/ room nobody throws out the old furniture when it's replaced, and the moving company packs "the whole room, dust included" — the more old stuff, the heavier the container.

Fix: prepend cleanup to the build script (package.json):

"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true});require('fs').rmSync('dist-electron',{recursive:true,force:true})\" && tsc && vite build && electron-builder"
json

Minor issues worth fixing while you're at it

  • .gitignore gap (a scaffold bug): the scaffold does carry logic to append dist-electron and release to .gitignore, but it looks for the insertion point via exact whole-line matching while the template file uses CRLF endings — the match never succeeds, so the patch silently fails and every variant's .gitignore misses those two lines, and build artifacts end up committed to git. Add them manually;
  • Placeholder metadata: appId: "YourAppID" and productName: "YourAppName" in electron-builder.json5 are scaffold placeholders that directly determine the installer filename (the actual artifact was YourAppName-Windows-0.0.0-Setup.exe) and install directory — set real values;
  • Metadata warnings: description is missed / author is missed — fill both fields in package.json; harmless to the build;
  • Icon: default Electron icon is used — drop an icon.png (≥256×256) into build/ at the project root and electron-builder picks it up automatically.

The unified fix list (paste it to your AI agent in one go)

The fixes above are identical across all three templates. Paste the whole block to an AI agent in one shot (or do it by hand — ten minutes):

This project was generated by create-electron-vite 0.7.1. Apply the following fixes in order,
running npx tsc --noEmit after each step to verify:

1. Remove the unused require declaration in electron/main.ts:
   the lines "import { createRequire } from 'node:module'" and "const require = createRequire(import.meta.url)"
2. Partition dependencies: move renderer-only dependencies (react, react-dom; vue for Vue projects)
   from dependencies to devDependencies; keep only main-process runtime packages in dependencies
3. Prepend artifact cleanup to the build script:
   node -e "require('fs').rmSync('dist',{recursive:true,force:true});require('fs').rmSync('dist-electron',{recursive:true,force:true})" &&
4. Add to electron-builder.json5:
   "electronDownload": { "mirror": "https://npmmirror.com/mirrors/electron/" }
5. Append two lines to .gitignore: dist-electron/ and release/
   (the scaffold's auto-patch silently fails on CRLF)
6. Replace the YourAppID / YourAppName placeholders in electron-builder.json5 with the real app name

When done, run npm run build once to confirm it passes, and report the asar size.

How to verify after fixing

  1. npm run build shows no download logs (or a successful npmmirror download);
  2. ls dist-electron/ matches expectations (for a template project: main.js + preload.mjs + your own chunks) — no oddly-named orphans;
  3. asar size is sane: template-level projects < 1 MB; pure templates may not even have a node_modules directory;
  4. Smoke test: run release/<version>/win-unpacked/<app>.exe directly — if the window comes up, main-process dependencies are complete.

If you're stuck on more than these four Electron build issues — or you'd rather have someone straighten the project out for you — let's talk.

Related posts

Was this article helpful?

Questions, corrections, or your own take. We read every piece of feedback.

Let's talk about your project

Most of the problems in this article — we've stepped in them and fixed them ourselves. Arshtech builds web systems, desktop software, and AI-assisted delivery for small businesses, working remotely at a per-project price.