Elijah Asaolu I am a programmer, I have a life.

Building an app with Next.js and Electron

6 min read 1904

Building An App With Next.js And Electron

Since the introduction of Node.js, basic web technologies (HTML, CSS, and JavaScript) have grown so much that we can now build multi-device/multi-platform applications with them. This massive development also resulted in the introduction of frameworks and libraries, which simplified our job even further. Next.js and Electron are significant examples on this list.

In this article, we’ll be exploring Nextron, a package that combines the powerful features of Next.js and Electron to enable us to create multi-platform desktop applications in the easiest and most enjoyable way ever.

Jump ahead:

Prerequisites

The following prerequisites are required to follow along with this tutorial:

  • Familiarity with JavaScript
  • Basic familiarity with the command line/terminal
  • Previous experience working with Next.js and Electron might help, but is not required

What is Electron.js used for?

Electron is an open-source JavaScript library that allows you to create cross-platform desktop applications using web technologies. Behind the scenes, Electron utilizes Node.js and the powerful Chromium engine to render applications created with basic web technologies as desktop applications. As a result, if you’re already familiar with the process of creating a website, you can also create a desktop application for different OS platforms with Electron.

What are the advantages of Electron.js?

Electron offers several advantages, such as:

  • Cross-platform: Electron allows you to create applications for major operating systems, such as Windows, macOS, and Linux
  • Easy to use: If you have basic HTML, CSS, and Javascript knowledge, you’ll find it easy to get started creating an Electron application
  • Desktop API: Electron enables you to easily access desktop/hardware APIs using web technologies you’re already familiar with

Despite Electron’s many advantages, it has some drawbacks as well:

  • Large app size: Electron is packaged with Chromium, which increases the size of bundled applications. In fact, a “Hello, World!” application written in Electron could be larger than 50MB
  • Performance: Electron applications are known to be resource hogs because they consume a lot of battery energy and system RAM

Using two fantastic technologies, Nextron enables us to rapidly create desktop applications. And, as you’ll see in this tutorial, creating a desktop application with Nextron is both simple and enjoyable.

Setting up your Nextron project

You can easily create a new Nextron application by running:

npx create-nextron-app appName

Nextron also provides various example templates that support TypeScript as well as popular CSS frameworks, such as Tailwind CSS, Ant Design, Material UI, and Emotion. All of the templates can be found here, and after deciding on which to choose for your project, you can install them by prefixing the installation command with the preferred example template along with the --example flag. So if you want to use the TypeScript and Tailwind templates, you’ll have to run:

npx create-nextron-app appName --example with-typescript-tailwindcss

Running the installation command will create all the necessary files and directories for our project. However, we’ll still need to install all the required dependencies with the command below:

npm install
# OR
yarn

Once this is completed, we can start our application with:

npm run dev
# OR
yarn dev

This will immediately open a new window for our application, as shown in the screenshot below:

Opening New Window For Our Application

You can also close the dev tool so that you are able to access the application fullscreen.

The file structure for a Nextron project

While there may be minor changes, mostly depending on the example template you choose during installation, the file structure for a typical Nextron project is as follows:

File Structure For Typical Nextron Project

And as you might have noticed, all the Next.js-related code is located inside the /render folder. Some other files and directories that you might not be familiar with include:

  • main/helpers — This directory exports a module called createWindow, which uses Electron’s BrowserWindow function to create our desktop application window. We can configure the default app window options here, such as its initial size and position
  • main/background.js — This file is where the createWindow function is initialized and is responsible for serving our Next.js application as an Electron application
  • resources/ — We may put our desktop application resources, such as app icons, here

How Nextron works

It’s pretty easy to grasp how Nextron works behind the scenes. It automatically checks if your application is running in development mode or in production. If in development mode, Nextron takes the localhost URL generated by running a Next.js application and serves it as an Electron app. However, if in production, Nextron takes the static files generated from a Next.js build and renders them instead.

Pages

Pages function exactly the same way as they would in a standard Next.js application, with no exception. To try things out, let’s create a new sample.js file inside the renderer/pages directory with the following content:

import Link from "next/link";

const sample = () => {
  return (
    <>
      <div>
        <h1>Hello world</h1>
        <p>Welcome to sample page</p>
        <Link href="/home">
          <a>Go Home</a>
        </Link>
      </div>
    </>
  );
};

export default sample;

And also update the default home.jsx file in this same directory with the content below, so that it links to our new page instead:

import Link from "next/link";

const Home = () => {
  return (
    <>
      <h1>Hello World</h1>
      <p>Welcome to my Nextron desktop app</p>
      <Link href="/sample">
        <a>Go to sample page</a>
      </Link>
    </>
  );
};

export default Home;

We have the following results:

Results For Nextron Desktop Application

Using layouts

Layouts operate just as they would in a regular Next.js application, with no exceptions either. For example, suppose we have a navbar that we want to share across multiple pages in our desktop application. First, we’ll need to create our layout file; for example, a new layout.js file within the /renderer directory with the following content:

const Layout = ({ children }) => {
  return (
    <>
      <nav>
        <h2>Nav Logo</h2>
        {/* more navbar code here */}
      </nav>
      <main>{children}</main>
    </>
  );
};

export default Layout;

And since Nextron doesn’t automatically include the _app.js file, which would normally come with an application created with the create-next-app package, we’ll have to create one manually as well and include our layout:

//_app.js
import Layout from "../layout";

export default function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  );
}

With a little CSS applied to the navbar and the pages we’ve previously generated, we’ll get the following results:

CSS Applied To Navbar

Creating new windows

While the use of this is very uncommon, it is also possible to programmatically create new windows. We would typically have used Electron’s BrowserWindow to accomplish this, but Nextron already exports a helper method called createWindow for us, which we can use instead.

Assume we want to open the /sample page we created earlier in a new window. We’ll need to first update main/background.js to incorporate support for this new window:

import { app, ipcMain } from "electron";
. . .

  const mainWindow = createWindow("main", {
    width: 1000,
    height: 600,
  });
  const sampleWindow = createWindow("sample", {
    width: 700,
    height: 400,
    show: false,
  });

  if (isProd) {
    await mainWindow.loadURL("app://./home.html");
    await sampleWindow.loadURL("app://./sample.html");
  } else {
    const port = process.argv[2];
    await mainWindow.loadURL(`http://localhost:${port}/home`);
    await sampleWindow.loadURL(`http://localhost:${port}/sample`);
  }

  ipcMain.on("show-sample", () => {
    sampleWindow.show();
  });
. . .

In the code provided above, immediately after creating the mainWindow, we create another window (sampleWindow), setting its initial width and height to 700×400 and also setting its show value to false so that this window is not visible by default. And using Electron’s ipcMain event emitter, we are listening for an event show-sample, so that when this event is emitted, we display the new sampleWindow.

The next step would be to emit this event programmatically, and we can also do that easily with ipc in a page. For example, in render/home.jsx:

import electron from "electron";
const ipcRenderer = electron.ipcRenderer || false;

const Home = () => {
  const openWindow = () => {
    ipcRenderer.send("show-sample");
  };

  return (
    <>
      <h1>Hello World</h1>
      <p>Welcome to my Nextron desktop app</p>
      <button onClick={openWindow}>Open sample page</button>
    </>
  );
};

export default Home;

If we run our application, we’ll have the following output:

Nextron Desktop App With New Window

Furthermore, we can also open an external URL in a new window by adding the target=_blank attribute or using a JavaScript window.open() method to open the target URL.

Electron APIs

Browser-based APIs and the Electron APIs work graciously too, and you can directly import them into any page. An example is the browser notification API, which we can utilize on a page like below:

const showNotification = () => {
  const notificationTitle = "My Notification 🔔";

  new Notification(notificationTitle, {
    body: "This is a sample notification.",
  }).onclick = () => console.log("Notification Clicked");
};

And any time we perform an action that triggers the showNotification function, we’ll immediately get a notification on our desktop. Here’s an example of a macOS notification:

MacOS Notification

Nextron webpack processes

Nextron applications via Next.js run on two different webpack processes, namely the client process and server process. With this in mind, it’s critical to determine which process you’re in, especially if you intend to use libraries that don’t support server-side rendering.

You should check if the current process is server or client. According to the Nextron documentation, here’s the recommended syntax for checking this for an Electron module:

import electron from 'electron';

const Home = () => {
  const moduleName = electron.moduleName || false;
  if (moduleName) {
    // we can use `electron.moduleName`
  }
};

export default Home;

Optionally, you can leverage React’s useEffect() Hook and the browser’s window to check if you’re currently on the client process, like so:

import { useEffect } from "react";

const Home = () => {
  useEffect(() => {
    if (typeof window === "undefined") {
      // we are on the client process.
    }
  }, []);
  return <div></div>;
};

export default Home;

Building a demo app

Once you’re done coding your application, you’ll definitely want to generate an executable file so that you can share the application with others. Nextron handles this with electron-builder. We can generate the executable for our Nextron project with the command below:

npm run build
# OR
yarn build

You can also update the script section in your package.json file to include other build options:

. . .
{
  "scripts": {
    "build": "nextron build",
    "build:all": "nextron build --all",
    "build:win32": "nextron build --win --ia32",
    "build:win64": "nextron build --win --x64",
    "build:mac": "nextron build --mac --x64",
    "build:linux": "nextron build --linux"
  }
}

So, if we want to generate an executable for Windows 64 bit, we’ll use the following command:

npm run build:win64
# OR
yarn build:64

Conclusion

In this article, we’ve looked at Nextron, a JavaScript package that allows us to effortlessly create desktop applications with Electron and Next.js. We looked at how to create new pages, application layout, and new windows, how to use Electron APIs, how to check which webpack process you’re in, and how to generate executable files for your application.



LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next.js app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — .

Elijah Asaolu I am a programmer, I have a life.

One Reply to “Building an app with Next.js and Electron”

Leave a Reply