Skip to content

plugin-vite: electron-forge start loads the renderer twice, as the first preload build sends a full-reload #4439

Open

Description

@kalinichenko88

Pre-flight checklist

  • I have read the contribution documentation for this project.
  • I agree to follow the code of conduct that this project uses.
  • I have searched the issue tracker for a bug that matches the one I want to file, without success.

Forge version

8.0.1 (same in 8.0.0-alpha.10)

Electron version

v44.5.1

Operating system

macOS 27.0.1

Last known working Forge version

No response

Expected behavior

electron-forge start loads the window's page once.

Actual behavior

It loads it twice. Whatever happens on the first load is lost; we hit it as a flaky UI check that acts on the page right after launch.

Steps to reproduce

npx create-electron-app@8.0.1 repro --template vite --typescript
cd repro

In src/main.ts, right after the BrowserWindow is created:

mainWindow.webContents.on('did-finish-load', () => console.log('did-finish-load'));
npm start

The terminal prints did-finish-load twice, in 3 runs out of 3.

Additional information

The preload's watch build sends reload-renderers from closeBundle on every build, the first one included (dist/subprocess-worker.js, target === 'preload'), and VitePlugin turns it into server.ws.send({ type: 'full-reload' }). On the first build Electron has not started yet, so no client is connected. Vite (8.3.3 here) keeps a full-reload that finds no client and sends it to the first client that connects (bufferedMessage in its createWebSocketServer), so the first page reloads right after it connects.

Not sending reload-renderers for the first build would fix it. Our workaround, a plugin in the renderer's Vite config:

{
  name: 'skip-forge-reload-before-first-page',
  configureServer(server) {
    let connected = false;
    server.ws.on('connection', () => {
      connected = true;
    });
    const send = server.ws.send.bind(server.ws) as (...args: unknown[]) => void;
    server.ws.send = (...args: unknown[]) => {
      if (connected || JSON.stringify(args[0]) !== '{"type":"full-reload"}') {
        send(...args);
      }
    };
  },
}

Activity

  1. added a commit that references this issue on Oct 21, 2026
    8cd6e8d
  2. dmoles commented on Oct 28, 2026

    @dmoles

    I'm seeing this as well, FWIW:

    Image

    It's making my startup logic, which involves RxJS Observables in the main process, with Observers that bridge them to IPC events, quite tricky -- initial events get sent when the window is created, and the original renderer process gets them, but the reloaded renderer process doesn't, because the window still exists and the main process doesn't know it was reloaded. My workaround is to have the renderer code post an explicit "OK, I'm ready now" event back to the main process, but it's messy, and I'm not sure there isn't still a race condition in there somewhere.

    Knowing that it only happens on the first load, though (and only in development), till the fix is in place, I may take that explicit event out and go back to trying to register the observers at an appropriate point in the window lifecycle — and just accept that I need to hit r in the terminal before I get the application in a consistent state. 🤷

  3. added a commit that references this issue on Nov 3, 2026
    6f0ebeb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions