# How OpenWhispr Implements URL-Based Routing for Dual-Window Electron Components

> Discover how OpenWhispr leverages URL-based routing with a window query parameter to render distinct component trees for its overlay and control panel using a single React bundle.

- Repository: [OpenWhispr/openwhispr](https://github.com/OpenWhispr/openwhispr)
- Tags: architecture
- Published: 2026-09-06

---

**OpenWhispr distinguishes its overlay and control-panel windows by injecting a `window` query parameter into the load URL, allowing a single React bundle to conditionally render entirely different component trees.**

OpenWhispr is an open-source Electron application that manages two distinct UI windows—a minimal dictation overlay and a full-featured control panel—from one shared React codebase. By implementing URL-based routing through query-string flags, the application eliminates redundant builds while ensuring both windows share global state and core utilities.

## Electron Main Process Configuration in [`src/main.js`](https://github.com/OpenWhispr/openwhispr/blob/main/src/main.js)

The Electron main process creates both windows in [`src/main.js`](https://github.com/OpenWhispr/openwhispr/blob/main/src/main.js), pointing each at the same bundled [`index.html`](https://github.com/OpenWhispr/openwhispr/blob/main/index.html) while appending a unique query string to differentiate their purpose. This approach relies on standard `BrowserWindow.loadURL` calls with injected parameters.

When creating the **overlay window** (a frameless, always-on-top dictation interface), the main process loads:

```js
// src/main.js – overlay window creation
const overlay = new BrowserWindow({
  width: 360,
  height: 80,
  frame: false,
  alwaysOnTop: true,
  transparent: true,
  webPreferences: { 
    preload: path.join(__dirname, "preload.js") 
  },
});

overlay.loadURL(`app://./index.html#/?window=overlay`);

```

For the **control-panel window** (the full settings and history interface), the process loads the same file with a different flag:

```js
// src/main.js – control-panel window creation
const panel = new BrowserWindow({
  width: 1024,
  height: 768,
  show: false,
  webPreferences: { 
    preload: path.join(__dirname, "preload.js") 
  },
});

panel.loadURL(`app://./index.html#/?window=panel`);
panel.once("ready-to-show", () => panel.show());

```

By varying only the `window` query parameter, the main process avoids conditional logic for asset loading and ensures both instances initialize with identical preload scripts and security contexts.

## React Router Implementation in [`src/App.jsx`](https://github.com/OpenWhispr/openwhispr/blob/main/src/App.jsx)

The renderer process handles URL-based routing inside [`src/App.jsx`](https://github.com/OpenWhispr/openwhispr/blob/main/src/App.jsx) by reading the query string at runtime. The component wraps the application in a `BrowserRouter` from react-router-dom, then parses the `window` parameter to determine which root component to mount.

### Parsing the Window Query Parameter

The router extracts the flag using the `useLocation` hook and `URLSearchParams`:

```tsx
// src/App.jsx
import { BrowserRouter, Routes, Route, useLocation } from "react-router-dom";
import OverlayWindow from "./components/OverlayWindow";
import ControlPanel from "./components/ControlPanel";

function App() {
  const { search } = useLocation();
  const params = new URLSearchParams(search);
  const win = params.get("window"); // "overlay" or "panel"

  return (
    <Routes>
      {win === "overlay" && (
        <Route path="*" element={<OverlayWindow />} />
      )}
      
      {win === "panel" && (
        <Route path="*" element={<ControlPanel />} />
      )}
    </Routes>
  );
}

```

### Conditional Route Rendering

The routing logic does not rely on URL path matching; instead, it evaluates the `window` variable to render the appropriate component tree. This architecture means:

- **Single entry point:** Both windows execute the same [`App.jsx`](https://github.com/OpenWhispr/openwhispr/blob/main/App.jsx) initialization code.
- **Shared providers:** Redux, Zustand, or React Context providers wrap both routes, enabling instantaneous state synchronization between windows.
- **Wildcard paths:** Each route uses `path="*"` to capture all navigation within that window's scope.

## Window-Specific Component Architecture

The URL-based routing strategy delegates all UI differentiation to the component layer, keeping the main process lean.

### The Overlay Window ([`src/components/OverlayWindow.jsx`](https://github.com/OpenWhispr/openwhispr/blob/main/src/components/OverlayWindow.jsx))

When the `window` parameter equals `"overlay"`, [`src/App.jsx`](https://github.com/OpenWhispr/openwhispr/blob/main/src/App.jsx) renders [`OverlayWindow.jsx`](https://github.com/OpenWhispr/openwhispr/blob/main/OverlayWindow.jsx). This component presents a minimal chrome-less interface containing the dictation pill, microphone status indicator, and a compact toolbar. The component assumes a small viewport (360×80 pixels) and handles transparent background rendering for seamless desktop integration.

### The Control Panel ([`src/components/ControlPanel.tsx`](https://github.com/OpenWhispr/openwhispr/blob/main/src/components/ControlPanel.tsx))

When the parameter equals `"panel"`, the router mounts [`ControlPanel.tsx`](https://github.com/OpenWhispr/openwhispr/blob/main/ControlPanel.tsx) from [`src/components/ControlPanel.tsx`](https://github.com/OpenWhispr/openwhispr/blob/main/src/components/ControlPanel.tsx). This component provides the full-screen settings interface, including model selection, transcription history, and configuration screens. It manages larger layouts (1024×768 pixels) and complex navigation stacks within its own window instance.

## Why URL-Based Routing?

OpenWhispr chose URL-based routing over separate HTML entry points or conditional module loading for three critical reasons:

- **Single JavaScript bundle:** Both windows load identical assets, reducing build size and ensuring shared utilities (hot-key managers, API clients) are parsed only once.
- **Automatic state sharing:** Because both windows run the same renderer process context, global stores (Redux or Zustand) remain synchronized without IPC overhead—settings changed in the panel instantly reflect in the overlay.
- **Simplified window creation:** The Electron side only modifies the load URL; no complex branching logic or multiple preload scripts are required to support distinct window types.

## Summary

- OpenWhispr creates dual windows in [`src/main.js`](https://github.com/OpenWhispr/openwhispr/blob/main/src/main.js) by calling `loadURL()` with `?window=overlay` or `?window=panel` query strings.
- The React layer in [`src/App.jsx`](https://github.com/OpenWhispr/openwhispr/blob/main/src/App.jsx) parses the query parameter via `URLSearchParams` and conditionally renders either `OverlayWindow` or `ControlPanel`.
- Both windows share a single codebase and global state, eliminating build redundancy while maintaining distinct UI architectures.
- This URL-based routing approach scales cleanly because the main process only needs to append new query flags to support additional window types.

## Frequently Asked Questions

### How does OpenWhispr differentiate between its two windows?

The application appends a `window` query parameter to the load URL in the Electron main process. The overlay loads `app://./index.html#/?window=overlay`, while the control panel loads `app://./index.html#/?window=panel`. The React router in [`src/App.jsx`](https://github.com/OpenWhispr/openwhispr/blob/main/src/App.jsx) reads this parameter to determine which component tree to render.

### Why use URL query parameters instead of separate HTML files?

Using query parameters allows both windows to share a single JavaScript bundle and asset set, significantly reducing application size. It also ensures that global state stores (Redux or Zustand) are automatically shared between windows since they run the same renderer context, eliminating the need for complex IPC synchronization.

### Can both windows access the same global state?

Yes. Because URL-based routing routes both windows through the same React entry point and bundle, they share the same JavaScript execution context. Global state providers wrap both the `OverlayWindow` and `ControlPanel` components, allowing real-time state updates across windows without additional messaging overhead.

### Is this routing approach scalable for additional window types?

Absolutely. To add a third window type (for example, a history viewer), the main process would simply call `loadURL()` with a new query parameter like `?window=history`, and [`src/App.jsx`](https://github.com/OpenWhispr/openwhispr/blob/main/src/App.jsx) would add a corresponding conditional route to render the new component. No changes to the build configuration or webpack entry points are necessary.