Vite and React
An Electron page is an ordinary web page, so it can be written in React, Vue or another framework. This needs a bundler that turns components into JavaScript for the browser. Electron Forge can create a project with the Vite bundler: it builds the page, the main process and preload.
Creating the project
npm init electron-app@latest notes-react -- --template=vite
cd notes-react
npm startFor TypeScript there is the vite-typescript template. After npm start Vite runs a dev server, and Electron opens a window that loads the page from it.
Structure
notes-react/
├── src/
│ ├── main.js the main process
│ ├── preload.js the preload script
│ ├── renderer.js page code
│ └── index.css
├── index.html
├── forge.config.js
├── vite.main.config.mjs
├── vite.preload.config.mjs
└── vite.renderer.config.mjsEach part has its own Vite config file. In this template the main process uses import instead of require: Vite bundles it into a single CommonJS file anyway. Within one project, stick to one style.
Loading the page
In src/main.js the window loads the page like this:
// src/main.js
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
} else {
mainWindow.loadFile(path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`));
}Forge fills in the constants. During development the window opens the Vite server address, so component changes show up at once. In the built app the finished file is loaded.
Adding React
npm install react react-dom
npm install --save-dev @vitejs/plugin-reactAdd the plugin to the page build config:
// vite.renderer.config.mjs
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
});Rename src/renderer.js to src/renderer.jsx, fix its path in the script tag in index.html, and add a <div id="root"></div> element to body.
A component
// src/renderer.jsx
import { createRoot } from 'react-dom/client';
import App from './App.jsx';
createRoot(document.getElementById('root')).render(<App />);// src/App.jsx
import { useEffect, useState } from 'react';
export default function App() {
const [notes, setNotes] = useState([]);
useEffect(() => {
window.notes.load().then(setNotes);
}, []);
return (
<ul>
{notes.map((note, i) => <li key={i}>{note}</li>)}
</ul>
);
}The component gets notes through the same window.notes as before: IPC handlers go into src/main.js, and the bridge into src/preload.js. After npm start the list appears in the window.
Vue and other options
For Vue, install vue and @vitejs/plugin-vue and add the vue() plugin in the same file. There is also a separate tool, electron-vite, with templates for React, Vue and Svelte — it works the same way.
Common mistake
Importing fs or path in a React component does not give file access: page code is built for the browser, and Vite either fails or substitutes an empty stub. React is only the renderer process. Keep everything that needs Node.js in the main process and call it through preload.