A modern, full-stack starter template combining Next.js 16 with React 19 for web applications and Electron + electron-builder for cross-platform desktop applications. Built with TypeScript, Tailwind CSS v4, and shadcn/ui components.
- ⚡️ Next.js 16 with App Router and React 19
- 🖥️ Electron for native desktop applications (Windows, macOS, Linux) packaged via electron-builder
- 🎨 Tailwind CSS v4 with CSS variables and dark mode support
- 🧩 shadcn/ui component library with Radix UI primitives
- 📦 Zustand for lightweight state management
- 🔤 Geist Font optimized with next/font
- 🎯 TypeScript for type safety
- 🎭 Lucide Icons for beautiful iconography
- 📚 Fumadocs documentation site as a pnpm workspace subpackage
- 📱 Dual deployment: Web app OR Desktop app from the same codebase
Before you begin, ensure you have the following installed:
-
Node.js 20.x or later (Download)
-
pnpm 8.x or later (recommended) or npm/yarn
npm install -g pnpm
No additional native toolchain is required. pnpm install downloads the appropriate Electron binary automatically (Electron is whitelisted in pnpm.onlyBuiltDependencies so its postinstall script can run). electron-builder handles platform-specific packaging without Rust, Python, or Visual Studio Build Tools on Windows / Linux. macOS code-signing & notarization are optional and configured the same way regardless.
-
Clone the repository
git clone <your-repo-url> cd react-quick-starter
-
Install dependencies
pnpm install # or npm install # or yarn install
-
Verify installation
# Check if Next.js is ready (web mode) pnpm dev # Compile the Electron main process (sanity check) pnpm electron:compile
pnpm dev
# or
npm run devThis starts the Next.js development server at http://localhost:3000. The page auto-reloads when you edit files.
app/page.tsx- Main landing pageapp/layout.tsx- Root layout with global configurationapp/globals.css- Global styles and Tailwind configurationcomponents/ui/- Reusable UI components (shadcn/ui)lib/utils.ts- Utility functions
pnpm electron:devThis command runs concurrently to:
- Start the Next.js development server at
http://localhost:3000 - Wait for the server to be ready (
wait-on), then compile the Electron main process viatsc - Launch Electron pointing at
ELECTRON_START_URL=http://localhost:3000 - Enable hot-reload for the Next.js renderer (the main process must be recompiled with
pnpm electron:compileif you change files inelectron/)
electron/main.ts- Main process:BrowserWindow, app lifecycle, CSP viasession.webRequest.onHeadersReceived, IPC handlerselectron/preload.ts-contextBridge.exposeInMainWorld('electronAPI', {...})exposes a typed surface to the rendererelectron/ipc/<name>.ts- Pure IPC handler functions (unit-testable)electron/tsconfig.json- CommonJS compile config; outputs todist-electron/electron/icons/- Build icons (icon.ico,icon.icns,icon.png)package.jsonbuildblock - electron-builder configuration
The template ships a typed IPC bridge demo (greet). Pattern for adding a new IPC command:
-
Write a pure handler in
electron/ipc/my-command.ts:export function myCommand(arg: string): string { if (!arg.trim()) throw new Error("arg cannot be empty") return `got ${arg}` }
-
Register it in
electron/main.tsinsideregisterIpc():import { myCommand } from "./ipc/my-command" ipcMain.handle("my-command", (_event, arg: unknown) => { if (typeof arg !== "string") throw new Error("arg must be a string") return myCommand(arg) })
-
Expose it in
electron/preload.ts:const electronAPI = { greet: (name: string) => ipcRenderer.invoke("greet", name), myCommand: (arg: string) => ipcRenderer.invoke("my-command", arg), } as const
-
Augment the type in
types/electron.d.ts:declare global { interface Window { electronAPI?: { greet: (name: string) => Promise<string> myCommand: (arg: string) => Promise<string> } } }
-
Add a typed wrapper in
lib/electron.ts:export async function myCommand(arg: string): Promise<string> { if (!window.electronAPI) { throw new Error("myCommand() invoked while not running in Electron") } return window.electronAPI.myCommand(arg) }
lib/electron.ts is the single point that calls window.electronAPI — business code imports named functions from it. Use isElectron() to gate any code path that depends on the desktop runtime.
| Command | Description |
|---|---|
pnpm dev |
Start Next.js development server on port 3000 |
pnpm build |
Build Next.js app for production (outputs to out/ directory) |
pnpm start |
Start Next.js production server (after pnpm build) |
pnpm lint |
Run ESLint to check code quality |
pnpm lint:fix |
Auto-fix ESLint issues |
pnpm format |
Format all files with Prettier |
pnpm format:check |
Check formatting without writing |
pnpm typecheck |
Run TypeScript type-check (no emit) |
pnpm test |
Run Jest unit tests |
pnpm test:watch |
Run Jest in watch mode |
pnpm test:coverage |
Run Jest with coverage report |
| Command | Description |
|---|---|
pnpm electron:dev |
Start Next.js + Electron concurrently with hot reload |
pnpm electron:compile |
Compile main+preload TypeScript → dist-electron/ |
pnpm electron:compile:watch |
Watch mode for the Electron compile step |
pnpm electron:build |
Build production installers for the current platform |
pnpm electron:build:win |
Build Windows installers (NSIS + MSI) |
pnpm electron:build:mac |
Build macOS bundles (DMG + ZIP, x64 + arm64) |
pnpm electron:build:linux |
Build Linux packages (AppImage + deb) |
| Command | Description |
|---|---|
pnpm docs:dev |
Start Fumadocs dev server on port 3001 |
pnpm docs:build |
Build docs for production (docs/.next/) |
pnpm docs:start |
Start docs production server on port 3001 |
# Add a new component (e.g., Card)
pnpm dlx shadcn@latest add card
# Add multiple components
pnpm dlx shadcn@latest add button card dialogreact-quick-starter/
├── app/ # Next.js App Router (main app)
│ ├── layout.tsx # Root layout with fonts and metadata
│ ├── page.tsx # Main landing page
│ ├── globals.css # Global styles and Tailwind config
│ └── favicon.ico # App favicon
├── components/ # React components
│ └── ui/ # shadcn/ui components (Button, etc.)
├── lib/ # Utility functions
│ └── utils.ts # Helper functions (cn, etc.)
├── public/ # Static assets (images, SVGs)
├── electron/ # Electron desktop application
│ ├── main.ts # Main process entry point (BrowserWindow, CSP, IPC)
│ ├── preload.ts # contextBridge exposure of typed API to renderer
│ ├── ipc/ # Pure IPC handler functions (unit-testable)
│ │ └── greet.ts
│ ├── icons/ # App icons (ico, icns, png) for electron-builder
│ └── tsconfig.json # CommonJS compile config (outputs to dist-electron/)
├── types/
│ └── electron.d.ts # Renderer-side `Window.electronAPI` type augmentation
├── dist-electron/ # Compiled main+preload (gitignored)
├── release/ # electron-builder output: installers (gitignored)
├── docs/ # Fumadocs documentation site (workspace package)
│ ├── app/ # Next.js App Router for docs
│ │ ├── layout.tsx # Root layout with RootProvider
│ │ ├── page.tsx # Redirect to /docs
│ │ ├── global.css # Tailwind v4 + Fumadocs theme
│ │ ├── docs/ # Docs routes
│ │ │ ├── layout.tsx # DocsLayout with sidebar
│ │ │ └── [[...slug]]/ # Dynamic MDX page
│ │ └── api/search/ # Orama search API route
│ ├── lib/source.ts # Fumadocs content loader
│ ├── content/docs/ # MDX content files
│ ├── source.config.ts # Content collection config
│ ├── next.config.ts # Next.js config (no static export)
│ └── package.json # Docs package dependencies
├── pnpm-workspace.yaml # pnpm monorepo config
├── components.json # shadcn/ui configuration
├── next.config.ts # Next.js configuration (main app)
├── tsconfig.json # TypeScript configuration
├── eslint.config.mjs # ESLint configuration
└── package.json # Root dependencies and scripts
Copy .env.example to .env.local to start:
cp .env.example .env.localThen edit .env.local to fill in your values. The lib/env.ts module validates required vars at first access.
Important:
- Only variables prefixed with
NEXT_PUBLIC_are exposed to the browser - Never commit
.env.localto version control - Use
.env.exampleto document required variables
The desktop app is configured in two places:
Window/lifecycle/IPC — electron/main.ts (createWindow()):
const win = new BrowserWindow({
width: 800,
height: 600,
title: "react-quick-starter",
resizable: true,
fullscreen: false,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
},
})Packaging — the build block in package.json (electron-builder):
Configured in components.json and tsconfig.json:
import { Button } from "@/components/ui/button"
import { cn } from "@/lib/utils"Available aliases:
@/components→components/@/lib→lib/@/ui→components/ui/@/hooks→hooks/@/utils→lib/utils.ts
The project uses Tailwind CSS v4 with:
- CSS variables for theming (defined in
app/globals.css) - Dark mode support via
classstrategy - Custom color palette using CSS variables
- shadcn/ui styling system
# Build static export
pnpm build
# Output directory: out/
# Deploy the out/ directory to any static hosting serviceThe build creates a static export in the out/ directory, optimized for production.
# Build for current platform (NSIS+MSI on Windows, DMG+ZIP on macOS, AppImage+deb on Linux)
pnpm electron:build
# Output: release/Platform-specific shortcuts:
pnpm electron:build:win # Windows: NSIS + MSI
pnpm electron:build:mac # macOS: DMG + ZIP (x64 + arm64)
pnpm electron:build:linux # Linux: AppImage + debDirect electron-builder invocation (for advanced flags, e.g. --publish=always):
pnpm build && pnpm electron:compile && pnpm exec electron-builder --win --x64 --publish=neverThe docs site (docs/) is a full Next.js server application deployed independently from the main app.
# Build docs
pnpm docs:build
# Output: docs/.next/
# Deploy to any Node.js host: Vercel, Netlify, Railway, etc.On Vercel, set the root directory to docs/ when importing the project.
- Push your code to GitHub/GitLab/Bitbucket
- Import project on Vercel
- Vercel auto-detects Next.js and deploys
# Build command
pnpm build
# Publish directory
out- Build the project:
pnpm build - Upload the
out/directory to your server - Configure server to serve static files
- Distribute
.exe(NSIS, recommended) or.msifromrelease/ - Users run the installer; NSIS is configured with
oneClick: falseandallowToChangeInstallationDirectory: true
- Distribute the
.dmgfromrelease/ - Users drag the app to Applications folder
- Note: For distribution outside the App Store, sign the app with a Developer ID Application certificate. Configure under
build.mac.hardenedRuntime/build.mac.entitlementsinpackage.jsonand provide signing secrets via env (seeCI_CD.md).
- Distribute the
.AppImagefromrelease/ - Users make it executable and run:
chmod +x app.AppImage && ./app.AppImage - Alternative format:
.deb(Debian/Ubuntu) — also produced
- Windows: Provide
CSC_LINK(PFX) +CSC_KEY_PASSWORDenv vars (or configure underbuild.wininpackage.json) - macOS: Requires an Apple Developer account; provide
APPLE_ID,APPLE_APP_SPECIFIC_PASSWORD,APPLE_TEAM_IDfor notarization - Linux: Optional
See the electron-builder Code Signing Guide for detailed instructions.
-
Start development server
pnpm dev # For web development # or pnpm electron:dev # For desktop development
-
Make changes
- Edit files in
app/,components/, orlib/ - Changes auto-reload in the browser/desktop app
- Edit files in
-
Add new components
pnpm dlx shadcn@latest add [component-name]
-
Lint your code
pnpm lint
-
Build and test
pnpm build # Test web/static-export build pnpm electron:build # Test desktop build for current platform
- Code Style: Follow ESLint rules (
pnpm lint) - Commits: Conventional Commits are enforced via the
commit-msghook (commitlint). After cloning, runpnpm installonce — thepreparescript auto-installs the hooks. - Components: Keep components small and reusable
- State: Use Zustand for global state, React hooks for local state
- Styling: Use Tailwind utility classes, avoid custom CSS when possible
- Types: Leverage TypeScript for type safety
Port 3000 already in use
# Kill the process using port 3000
# Windows
netstat -ano | findstr :3000
taskkill /PID <PID> /F
# macOS/Linux
lsof -ti:3000 | xargs kill -9Electron build fails
# Most failures come from a stale install or missing compiled main process.
# Clean and rebuild:
rm -rf node_modules dist-electron release
pnpm install
pnpm electron:compile
pnpm electron:buildModule not found errors
# Clear Next.js cache
rm -rf .next
# Reinstall all workspace dependencies
rm -rf node_modules docs/node_modules pnpm-lock.yaml
pnpm installCannot find module 'collections/server' in docs
This module is auto-generated by fumadocs-mdx. Run the docs dev server once to generate it:
pnpm docs:dev- Next.js Documentation - Learn about Next.js features and API
- Learn Next.js - Interactive Next.js tutorial
- Next.js GitHub - Next.js repository
- Electron Documentation - Official Electron documentation
- electron-builder Documentation - Packaging & code-signing guide
- Electron GitHub - Electron repository
- shadcn/ui - Component library documentation
- Tailwind CSS - Tailwind CSS documentation
- Radix UI - Radix UI primitives
- Zustand - Zustand documentation
- Fumadocs - Fumadocs documentation framework
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is open source and available under the MIT License.
If you encounter any issues or have questions:
- Check the Troubleshooting section
- Review Next.js Documentation
- Review Electron Documentation
- Review electron-builder Documentation
- Open an issue on GitHub
{ "build": { "appId": "com.reactquickstarter.desktop", "productName": "react-quick-starter", "asar": true, "directories": { "output": "release", "buildResources": "electron/icons" }, "files": ["dist-electron/**/*", "out/**/*", "package.json"], "win": { "target": [{ "target": "nsis" }, { "target": "msi" }], "icon": "electron/icons/icon.ico", }, "mac": { "target": [ { "target": "dmg", "arch": ["x64", "arm64"] }, { "target": "zip", "arch": ["x64", "arm64"] }, ], "icon": "electron/icons/icon.icns", }, "linux": { "target": [{ "target": "AppImage" }, { "target": "deb" }], "icon": "electron/icons/icon.png", }, }, }