This boilerplate demonstrates a turborepo setup combining Next.js with Electron, allowing you to use the same codebase with SSR (Server-Side Rendering)/ React Server Components(RSC) for Electron applications.
-
Next.js App -
next.config.mjs
- For the Next.js app, configure it by setting the output to "standalone":
/** @type {import('next').NextConfig} */ module.exports = { transpilePackages: ["@nextelectron/ui"], + output: "standalone", };
- This configuration produces a minimal bundle usable in Electron. Additionally, this output can be utilized in a Dockerfile to create a containerized version of your app, which is small in size and can run anywhere, not just Vercel.
- For the Next.js app, configure it by setting the output to "standalone":
-
Electron App -
package.json
- The Electron app uses the Next.js app as the renderer, relying on stock Electron without external libraries.
- Electron
build
configuration inpackage.json
:"build": { "asar": true, "executableName": "NextJSElectron", "appId": "com.saybackend.nextjs-electron", "asarUnpack": [ "node_modules/next", "node_modules/@img", "node_modules/sharp", "**\\*.{node,dll}" ], "files": [ "build", { "from": ".next/standalone", "to": "app", "filter": [ "!**/.env", "!**/package.json" ] }, { "from": ".next/static", "to": "app/.next/static" }, { "from": "public", "to": "app/public" } ], "win": { "target": [ "nsis" ] }, "linux": { "target": [ "deb" ], "category": "Development" } }
- The Next.js app output is copied to the Electron app to be used as the renderer during the build process.
- Main Electron file (
main.ts
):import { is } from "@electron-toolkit/utils"; import { app, BrowserWindow, ipcMain } from "electron"; import { getPort } from "get-port-please"; import { startServer } from "next/dist/server/lib/start-server"; import { join } from "path"; const createWindow = () => { const mainWindow = new BrowserWindow({ width: 900, height: 670, webPreferences: { preload: join(__dirname, "preload.js"), nodeIntegration: true, }, }); mainWindow.on("ready-to-show", () => mainWindow.show()); const loadURL = async () => { if (is.dev) { mainWindow.loadURL("http://localhost:3000"); } else { try { const port = await startNextJSServer(); console.log("Next.js server started on port:", port); mainWindow.loadURL(`http://localhost:${port}`); } catch (error) { console.error("Error starting Next.js server:", error); } } }; loadURL(); return mainWindow; }; const startNextJSServer = async () => { try { const nextJSPort = await getPort({ portRange: [30_011, 50_000] }); const webDir = join(app.getAppPath(), "app"); await startServer({ dir: webDir, isDev: false, hostname: "localhost", port: nextJSPort, customServer: true, allowRetry: false, keepAliveTimeout: 5000, minimalMode: true, }); return nextJSPort; } catch (error) { console.error("Error starting Next.js server:", error); throw error; } }; app.whenReady().then(() => { createWindow(); ipcMain.on("ping", () => console.log("pong")); app.on("activate", () => { if (BrowserWindow.getAllWindows().length === 0) createWindow(); }); }); app.on("window-all-closed", () => { if (process.platform !== "darwin") app.quit(); });
- Unlike traditional Electron setups where
index.html
is loaded, this setup runs the Next.js app on a different port and loads it, enabling SSR in Electron. get-port-please
is used to get a free port, starting the Next.js server on that port. This allows the same codebase to be used for both web and Electron.
To run the application, use the following commands:
make next_dev
make electron_dev
To build the Electron app:
make electron_dist
Note: Electron expects the Next.js app's production output to be present and copied to the Electron app. Ensure you have built the Next.js app before running the Electron app.
-
Development Server: Starts the development server for Next.js.
make next_dev
-
Build: Builds the Next.js project for production.
make next_build
-
Start: Starts the Next.js project.
make next_start
-
Lint: Lints the Next.js project.
make next_lint
- Format Code: Formats the codebase using
dprint
.make format
-
Postinstall: Installs app dependencies for Electron.
make postinstall
-
Build for Distribution: Builds Electron for distribution in directory mode.
make electron_dist
-
Build for Debian Distribution: Builds Electron for Debian distribution.
make electron_dist_deb
-
Build Using tsup: Builds Electron using
tsup
.make electron_build
-
Watch Mode: Watch mode for Electron with
tsup
.make electron_build_watch
-
Development Mode: Starts development mode for Electron.
make electron_dev
-
Build All: Builds both Next.js and Electron projects.
make build
-
Distribute All: Distributes both Next.js and Electron projects.
make dist
-
Development Mode for All: Starts development mode for both Electron and Next.js.
make dev
To use any of the tasks, simply run the corresponding make
command in your terminal. For example, to start the development server for Next.js, you would run:
make next_dev
For a list of all available tasks, you can run:
make help
This will display a summary of all tasks and their descriptions.
- Ensure all dependencies are installed using
pnpm
before running the tasks. - The tasks are defined in the
Makefile
for ease of use and to maintain consistency across development and build processes.