A modern, type-safe Electron starter template with the latest technologies, pre-configured and ready for production.
Setting up a modern Electron app with current best tools and practices was unnecessarily painful. So I am putting this template out there to make it easier for others. It uses tanstack router for Next.js like app routing in the renderer process and tRPC for a type-safe API layer in the main process to simplify the whole preloader and ipc communication.
| Category | Technology | Version |
|---|---|---|
| Runtime | ⚛️ Electron | 39.x |
| Framework | 💙 React | 19.x |
| Language | 📘 TypeScript | 5.x |
| Bundler | ⚡ Vite | 5.x |
| Build Tool | 🛠️ Electron Forge | 7.x |
| Styling | 🎨 TailwindCSS | 4.x |
| Routing | 🛣️ TanStack Router | 1.x |
| UI Components | 🧱 shadcn/ui | latest |
| API Layer | ⚡ tRPC v11 | latest |
# Clone the template
git clone https://github.com/yourusername/react-electron-template.git my-app
cd my-app
# Install dependencies (using Bun is recommended 🥟)
bun install
# Start development 🚀
bun startsrc/
├── 🔌 main/
│ ├── main.ts # Electron main process
│ └── trpc/
│ ├── context.ts # tRPC context
│ ├── router.ts # App router
│ ├── trpc.ts # tRPC init
│ └── routes/
│ └── health.ts # Health check route
├── 🌉 preload/
│ └── preload.ts # Preload script (IPC bridge)
└── ⚛️ renderer/
├── renderer.tsx # React entry point
├── routeTree.gen.ts # Auto-generated route tree
├── 📂 routes/
│ ├── __root.tsx # App layout (like App.tsx)
│ └── index.tsx # Home page (/)
├── 🧱 components/
│ ├── ui/ # shadcn/ui components
│ ├── theme-provider.tsx # Dark mode context
│ └── theme-toggle.tsx # Theme switcher
├── 🔧 lib/
│ ├── trpc.ts # tRPC client + react-query
│ └── utils.ts # Utility functions (cn, etc.)
└── 🎨 styles/
└── global.css # Tailwind + theme variables
Simply create a file in src/renderer/routes/ and it becomes a route!
// src/renderer/routes/settings.tsx → /settings
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/settings')({
component: Settings,
})
function Settings() {
return <div>⚙️ Settings Page</div>
}Nested routes? Easy:
src/renderer/routes/settings/index.tsx→/settingssrc/renderer/routes/settings/profile.tsx→/settings/profile
Create a file in src/main/trpc/routes/ and merge it into the app router:
// src/main/trpc/routes/users.ts
import { trpc } from '../trpc'
export const users = trpc.router({
list: trpc.procedure.query(() => [{ id: 1, name: 'Alice' }]),
})Then register it in src/main/trpc/router.ts:
import { users } from './routes/users'
export const appRouter = trpc.router({
health,
users,
})Call it from the renderer:
const { data } = trpc.users.list.useQuery()We use shadcn/ui. Add components via CLI:
bunx --bun shadcn@latest add button
bunx --bun shadcn@latest add card dialog inputtRPC is wired up via trpc-electron. All new routes will be defined in router file. Entry points to review:
src/main/trpc/*(router + context)src/main/main.ts(IPC handler inapp.whenReady())src/preload/preload.ts(bridge)src/renderer/lib/trpc.ts+src/renderer/renderer.tsx(client + providers)
Coming from a standard React project? Here's the map:
| Regular React | This Template |
|---|---|
src/main.tsx |
src/renderer/renderer.tsx |
src/App.tsx |
src/renderer/routes/__root.tsx |
src/pages/Home.tsx |
src/renderer/routes/index.tsx |
src/pages/About.tsx |
src/renderer/routes/about.tsx |
Key Difference: No manual router config—file location defines the route!
forge.config.ts- Electron Forge configvite.main.config.ts- Main process Vite configvite.renderer.config.ts- Renderer Vite configvite.preload.config.ts- Preload Vite configtsconfig.json- TypeScript configcomponents.json- shadcn/ui config
MIT
