Desktop Apps with Electron + Next.JS (without Nextron)
Tags: desktop, nextjs, electron, development
Published: 2023-11-29 05:21:22

![Next.JS inside Electron](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kza9k7poin48q9to2fly.png)

## A bit of story first

Some time ago I've started a personal project called Local.Host, which was a replacement alternative for XAMPP, for PHP developers using Windows. Due to my extensive knowledge of web development, I decided to use Electron for building the desktop app, alongside with some Windows' CMD commands and tricks.

After some search, I've found Nextron, a good Next.JS + Electron boilerplate. But, today, I've noticed by some friends that my software is a bit heavy, sometimes freezing. Understanding more about Electron, I've noticed that Nextron comes with an old version of both Electron and Next.JS, and some "internal" mechanisms used are causing slow downs on my application.

To not fully rewrite the app, I tried to "create" my own Next.JS + Electron project, with latest versions of both and, obviously, more lightweight than Nextron.

So, now, I have a (I personally consider it) very solid guide for creating desktop apps with these techs. And, of couse, I decided to share it with you, because I've found a lot of people that are searching for something like that and are all with the same opinion about Nextron.

## The guide

### Step 1 - Start a Next.JS project

Start with Next.JS. First, run the traditional command `npx create-next-app@latest <your_project_name>`. Set the preferences you want durint setup. The only setting you need to take care is about using App Router. Currently, I personally choose "No" to keep using the old-but-gold Pages Router, and it's the way I've used.

```bash
npx create-next-app@latest electron-nextjs-project
```

### Step 2 - Install Electron and other dependencies

`cd` into your project folder (in this example, `electron-nextjs-project`) and install the following dependencies, with both following commands:

Dev dependencies:
```bash
npm install --save-dev electron electron-builder concurrently
```

Project dependencies:
```bash
npm install electron-serve
```

### Step 3 - Setting up package.json

Start opening the `package.json` file with your preferred text editor or IDE. You need to modify the `build` and `dev` scripts, and add the `main` property. The concurrently we installed before will be used to run both Next.JS and Electron in parallel during the development, and you need to point the `main` script to the entrypoint of your Electron application. Also, we need to add "author" and "description" attributes, both needed when building the application executables.

```json
{
  ...
  "main": "main/main.js",
  "author": "RBFraphael",
  "description": "Electron + NextJS example project",
  "scripts": {
    "dev": "concurrently -n \"NEXT,ELECTRON\" -c \"yellow,blue\" --kill-others \"next dev\" \"electron .\"",
    "build": "next build && electron-builder",
    ...
  }
  ...
}
```

![package.json](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yn4uxi5bvueetjn80ftl.png)

As you can see, when developing with `npm run dev` will start both Next.JS and Electron (you can find more details about Concurrently [here](https://www.npmjs.com/package/concurrently)), and, when building your application with `npm run build` it will build the Next.JS files, then the Electron application (find more information about Electron Builder [here](https://www.electron.build/)).

### Step 4 - Configuring Next.JS

Since we're using Next.JS as the starting point, we should have a `next.config.js` file in the root of our project. In this file, we need to set the output mode to `export`, and disable the image optimization. So, inside this file, add the following params to the exported `nextConfig` object.

```javascript
const nextConfig = {
  ...
  output: "export",
  images: {
    unoptimized: true
  }
  ...
}
```

![next.config.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ux05por8io9rig70lr32.png)

### Step 5 - Electron bootstrap

Now, we will code the Electron part of our application. Start creating a folder called `main`, and two files inside that folder: `main.js` (as we set on the `main` key of our `package.json`) and `preload.js` (for exposing Electron to front-end), and insert the following content in the `main.js` file.

main.js
```javascript
const { app, BrowserWindow } = require("electron");
const serve = require("electron-serve");
const path = require("path");

const appServe = app.isPackaged ? serve({
  directory: path.join(__dirname, "../out")
}) : null;

const createWindow = () => {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      preload: path.join(__dirname, "preload.js")
    }
  });

  if (app.isPackaged) {
    appServe(win).then(() => {
      win.loadURL("app://-");
    });
  } else {
    win.loadURL("http://localhost:3000");
    win.webContents.openDevTools();
    win.webContents.on("did-fail-load", (e, code, desc) => {
      win.webContents.reloadIgnoringCache();
    });
  }
}

app.on("ready", () => {
    createWindow();
});

app.on("window-all-closed", () => {
    if(process.platform !== "darwin"){
        app.quit();
    }
});
```


![main.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bf902m55k3jwwh8fywzk.png)

As we can analyse, this code will use electron-serve to properly serve static files from the `out/` folder, but only when our app is packaged (builded, in production, compiled, whatever you want). While not packaged, we will run our app through the `http://localhost:3000` URL, which is the default URL for Next.JS projects. Also, we already prepared the script to load our `preload.js` file. Another point of this scripts is the "did-fail-load" event while development. We added it because sometimes Electron may start faster than Next.JS (remember they are called together with concurrently), so, in those moments, it will trigger an error that "URL cannot be loaded" or someting like that, and will give us only a blank screen. With this event implemented on our script, when this occurs, it will automatically reload the app content, until success when showing Next.JS content.

### Step 6 - Setting up the preload script

Now, we just need to expose the Electron for the React/Next.JS part of our application. It's very useful for using IPC messages, for example.

Inside the `main/preload.js` file we created before, insert the following content:

```javascript
const { contextBridge, ipcRenderer } = require("electron");

contextBridge.exposeInMainWorld("electronAPI", {
    on: (channel, callback) => {
        ipcRenderer.on(channel, callback);
    },
    send: (channel, args) => {
        ipcRenderer.send(channel, args);
    }
});
```

![preload.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g45vxa1vbw4h0v2hblj9.png)

This way, we can call `window.electronAPI.on` on Next.JS components to handle IPC data that comes from the "back-end" of our application, and `window.electronAPI.send` to send data to the "back-end" of our application.

### Step 7 - Testing

Finally, we can just run `npm run dev` on our terminal. If all things are well configured, we should see a beautiful Electron window with the starter content of Next.JS. Also, the built-in DevTools shoud appear. And, in the running terminal, we can see the debug from both Next and Electron, tagged with different colors too. Now is time to develop our application. Remember that we have hot reload for front-end (the renderer/Next part of our app), but, when making changes into the "back-end" (the main/Electron part of our application) we need to stop the whole application (a simple Ctrl+C on the terminal do the job) then run it again.

![Next.JS inside Electron](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kza9k7poin48q9to2fly.png)

### Step 8 - Building the executables

Now it's time to build our application executables. We're using electron-builder to handle that for us. Start creating a file called `electron-builder.yaml` on the root of our project. Then, inside this file, put the configuration for building the application, according to the [official electron-builder documentation](https://www.electron.build/). You can find an example below:

```yaml
appId: "io.github.rbfraphael.electron-next"
productName: "Electron Next.JS"
copyright: "Copyright (c) 2023 RBFraphael"
win:
  target: ["dir", "portable", "zip"]
  icon: "resources/icon.ico"
linux:
  target: ["dir", "appimage", "zip"]
  icon: "resources/icon.png"
mac:
  target: ["dir", "dmg", "zip"]
  icon: "resources/icon.icns"

```

![electron-builder.yaml](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mnxdk3go763xdrs591ic.png)

After properly specifying the needed options on `electron-builder.yaml` file, you just need to run `npm run build` on your terminal. The Next.JS static files will be generated and exported to the `out/` directory, then the Electron application will be compiled, and saved to the `dist/` directory.

![Generated files](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mfj2ytmk8ygd1q4fkaa6.png)

Now, when you run your application with the exported files, you should see a Electron window with your Next.JS app, now without the built-in DevTools.

![Final application](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sxybu1biwk2rx73ete9f.png)

## Conclusion

Using Next.JS to build desktop application with Electron is a good idea, specially when you want to use React alongside with Next.JS features, such as easy routing. And it shouldn't be a pain, and using Nextron it could be. So, the best approach (I personally believe) is to start from scratch. In this guide we learned an approach to handle that.

Hope this guide may help somebody :)

Thanks!