Skip to main content

Next.js

This guide will walk you through creating your first Tauri app using the React framework Next.js.

info

Before we continue, make sure you have completed the prerequisites to have a working development environment.

Tauri is a framework to build desktop applications with any frontend framework and a Rust core. Each app consists of two parts:

  1. Rust binary that creates the windows and exposes native functionality to those windows
  2. Frontend of your choice that produces the user interface inside the window

In the following, we will first scaffold the frontend, set up the Rust project, and lastly show you how to communicate between the two.

create-tauri-app

The easiest way to scaffold a new project is the create-tauri-app utility. It provides opiniated templates for vanilla HTML/CSS/JavaScript and many frontend frameworks like React, Svelte, and Yew.

sh <(curl https://create.tauri.app/sh)

Please note that you do not need to follow the rest of this guide if you use create-tauri-app, but we still recommend reading it to understand the setup.

Here's a preview of what we will be building:

Application Preview Application Preview

Create the Frontend

Next.js is a React Framework that comes with both Server-Side Rendering (SSR) and Static-Site Generation (SSG) capabilities. To make Next.js work with Tauri we are going to use the SSG mode since it generates only static files that can be included in the final binary.

Next.js comes with a scaffolding utility similar to create-tauri-app that can quickly setup a new project from a number of pre-defined templates. For this guide we will use the TypeScript template to create a simple project.

npx create-next-app@latest --use-npm --typescript
  1. Project name
    This will be the name of your project. It corresponds to the name of the folder this utility will create but has otherwise no effect on your app. You can use any name you want here.

When starting or building the frontend, Next.js will look for a config file named next.config.js inside the project root. We want to customize this file to get the best compatibility with Tauri.

Update the file with the following content:

next.config.js
/** @type {import('next').NextConfig} */

const nextConfig = {
reactStrictMode: true,
swcMinify: true,
// Note: This experimental feature is required to use NextJS Image in SSG mode.
// See https://nextjs.org/docs/messages/export-image-api for different workarounds.
images: {
unoptimized: true,
},
}

module.exports = nextConfig

To be able to build in production we must add a next export command in package.json. This will produce a static HTML/JavaScript version of your Next.js application in the out folder. We'll also add the tauri command to package.json.

Your package.json should look like this:

package.json
{
"scripts": {
"dev": "next dev",
"build": "next build",
"export": "next export",
"start": "next start",
"tauri": "tauri",
"lint": "next lint"
},

Next.js in SSG mode

The "benefit" of using SSG mode is pre-rendered React code in static HTML/JavaScript. This means your page can load faster. React doesn't have to render the HTML on the client-side but will hydrate it on the first load if needed. The "downside" is that we cannot use getServerSideProps or use any type of "data fetching" for rendering our page for a request.

tip

You can use getStaticProps to generate HTML/JavaScript pages at build time. Since it is executed in the Node.js context, calling the Tauri api won't work. You can learn more about Next.js SSG in the official documentation.

Create the Rust Project

At the heart of every Tauri app is a Rust binary that manages windows, the webview, and calls to the operating system through a Rust crate called tauri. This project is managed by Cargo, the official package manager and general-purpose build tool for Rust.

Our Tauri CLI uses Cargo under the hood so you rarely need to interact with it directly. Cargo has many more useful features that are not exposed through our CLI, such as testing, linting, and formatting, so please refer to their official docs for more.

Install Tauri CLI

If you haven't installed the Tauri CLI yet you can do so with one of the below commands. Aren't sure which to use? Check out the FAQ entry.

npm install --save-dev @tauri-apps/cli
For npm to detect Tauri correctly you need to add it to the "scripts" section in your package.json file:
package.json
"scripts": {
"tauri": "tauri"
}

To scaffold a minimal Rust project that is pre-configured to use Tauri, open a terminal and run the following command:

npm run tauri init

It will walk you through a series of questions:

  1. What is your app name?
    This will be the name of your final bundle and what the OS will call your app. You can use any name you want here.

  2. What should the window title be?
    This will be the title of the default main window. You can use any title you want here.

  3. Where are your web assets (HTML/CSS/JS) located relative to the <current dir>/src-tauri/tauri.conf.json file that will be created?
    This is the path that Tauri will load your frontend assets from when building for production.
    Use ../out for this value.

  4. What is the URL of your dev server?
    This can be either a URL or a file path that Tauri will load during development.
    Use http://localhost:3000 for this value.

  5. What is your frontend dev command?
    This is the command to used to start your frontend dev server.
    Use npm run dev for this value.

  6. What is your frontend build command?
    This the the command to build your frontend files.
    Use npm run build && npm run export for this value.
info

If you're familiar with Rust, you will notice that tauri init looks and works a lot like cargo init. You can just use cargo init and add the necessary Tauri dependencies if you prefer a fully manual setup.

The tauri init command generates a folder called src-tauri. It's a convention for Tauri apps to place all core-related files into this folder. Let's quickly run through the contents of this folder:

  • Cargo.toml
    Cargo's manifest file. You can declare Rust crates your app depends on, metadata about your app, and much more. For the full reference see Cargo's Manifest Format.

  • tauri.conf.json
    This file lets you configure and customize aspects of your Tauri application from the name of your app to the list of allowed APIs. See Tauri's API Configuration for the full list of supported options and in-depth explanations for each.

  • src/main.rs
    This is the entrypoint to your Rust program and the place where we bootstrap into Tauri. You will find two sections in it:

    src/main.rs
     #![cfg_attr(
    all(not(debug_assertions), target_os = "windows"),
    windows_subsystem = "windows"
    )]

    fn main() {
    tauri::Builder::default()
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
    }

    The line beginning with cfg! macro serves just one purpose: it disables the command prompt window that would normally pop up on Windows if you run a bundled app. If you're on Windows, try to comment it out and see what happens.

    The main function is the entry point and the first function that gets invoked when your program runs.

  • icons
    Chances are you want a snazzy icon for your app! To get you going quickly, we included a set of default icons. You should switch these out before publishing your application. Learn more about the various icon formats in Tauri's icons feature guide.

Now that we have scaffolded our frontend and initialized the Rust project you're almost ready to run your app. Your tauri.conf.json file should look something like this:

src-tauri/tauri.conf.json
{
"build": {
"beforeBuildCommand": "npm run build && npm run export",
"beforeDevCommand": "npm run dev",
"devPath": "http://localhost:3000",
"distDir": "../out"
},

And that's it! Now you can run the following command in your terminal to start a development build of your app:

npm run tauri dev

Application Window Application Window

Invoke Commands

Tauri lets you enhance your frontend with native capabilities. We call these Commands, essentially Rust functions that you can call from your frontend JavaScript. This enables you to handle heavy processing or calls to the OS in much more performant Rust code.

Let's make a simple example:

src-tauri/src/main.rs
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}

A Command is just like any regular Rust function, with the addition of the #[tauri::command] attribute macro that allows your function to communicate with the JavaScript context.

Lastly, we also need to tell Tauri about our newly created command so that it can route calls accordingly. This is done with the combination of the .invoke_handler() function and the generate_handler![] macro you can see below:

src-tauri/src/main.rs
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

Now you're ready to call your Command from the frontend!

To call our newly created command we will use the @tauri-apps/api JavaScript library. It provides access to core functionality such as window manipulation, the filesystem, and more through convenient JavaScript abstractions. You can install it using your favorite JavaScript package manager:

npm install @tauri-apps/api

With the library installed, you can modify your pages/index.ts file to call the Command:

pages/index.tsx
import { invoke } from '@tauri-apps/api/tauri'

// Note: When working with Next.js in development you have 2 execution contexts:
// - In server side where Tauri cannot be called because it's out of the context.
// - In client side where Tauri can be executed.
// To know if we are in server or client side we can do the following:
const isClient = typeof window !== 'undefined'

// Now we can call our Command!
// Right-click on the application background and open the developer tools.
// You will see "Hello, World!" printed in the console!
isClient &&
invoke('greet', { name: 'World' }).then(console.log).catch(console.error)

A better approach would be to use Tauri calls in componentDidMount or useEffect that are only run on the client-side by Next.js.

So to make it cleaner we should rewrite it like this:

pages/index.tsx
import { invoke } from "@tauri-apps/api/tauri"

const Home: NextPage = () => {
useEffect(() => {
invoke('greet', { name: 'World' })
.then(console.log)
.catch(console.error)
}, []);
tip

If you want to know more about the communication between Rust and JavaScript, please read the Tauri Inter-Process Communication guide.