How to Build a Desktop Application from NextChat Using Tauri

NextChat can be compiled into native desktop binaries for Windows, macOS, and Linux using the integrated Tauri framework that wraps the Next.js web interface in a Rust-powered runtime.

NextChat (ChatGPTNextWeb/NextChat) ships with a complete Tauri scaffold that enables packaging the React/Next.js frontend as a native application. This guide explains how to build a desktop application from NextChat using Tauri by examining the repository's Rust backend, custom HTTP streaming commands, and build configuration.

Architecture Overview

The Tauri implementation consists of five integrated layers that bridge the web UI with native system capabilities.

  • Web UI (Next.js): The original NextChat SPA located in app/... builds into static files that Tauri serves as the frontend.
  • Tauri Configuration: src-tauri/tauri.conf.json declares build commands, output directories, window settings, bundling options, and allowed APIs.
  • Rust Runtime: src-tauri/src/main.rs initializes the Tauri process, registers custom commands, and runs the event loop.
  • Custom Command: src-tauri/src/stream.rs implements stream_fetch, a Rust-side HTTP client that streams LLM API responses while bypassing browser CORS restrictions.
  • TypeScript Bridge: app/utils/stream.ts detects the Tauri environment and substitutes standard fetch calls with invocations to the Rust command.

Request Flow for LLM APIs

When the UI initiates an HTTP request, the data flows through a specialized pipeline that keeps network operations outside the browser sandbox.

  1. The UI calls fetch(url, options), which detects window.__TAURI__ and forwards the request via window.__TAURI__.invoke("stream_fetch", ...).
  2. The Rust command in src-tauri/src/stream.rs creates a reqwest::Client, sends the request, and emits raw byte chunks back to the web view using the Tauri event "stream-response" with ChunkPayload structs.
  3. The TypeScript bridge listens for these events, writes each chunk into a TransformStream, and resolves a standard Response object once the stream completes via an EndPayload signal.

This architecture allows the desktop application to perform arbitrary HTTP requests and stream large responses without browser security limitations.

Prerequisites and Environment Setup

Before building the desktop application, ensure your development environment meets the repository requirements.

  • Node.js: Version 18 or higher (as specified in the repository README).
  • Yarn: Version 1.x (the project uses Yarn for script execution).
  • Rust Toolchain: Install the stable channel via rustup to compile the Tauri backend.
  • Tauri CLI: The @tauri-apps/cli package is already listed as a dev dependency, so manual installation is optional.

Build Steps

Follow these steps to compile NextChat into distributable desktop binaries.

1. Install Dependencies

Clone the repository and install Node.js packages using Yarn.

git clone https://github.com/ChatGPTNextWeb/NextChat.git
cd NextChat
yarn install

2. Export Static Web Assets

Tauri requires the built UI in the ../out directory relative to src-tauri. The beforeBuildCommand in tauri.conf.json runs yarn export automatically, but you can generate the assets manually.

yarn export

This command builds the Next.js application and writes static files to ./out.

3. Run Development Mode with Hot Reload

Use the app:dev script to start the Next.js development server and launch Tauri in watch mode simultaneously.

yarn app:dev
  • The Next.js dev server runs on http://localhost:3000 as defined in tauri.conf.json.
  • Tauri loads this URL directly instead of the static out folder, enabling live code changes without rebuilding the Rust binary.

4. Create Production Builds

Generate platform-specific installers and executables using the app:build script.

yarn app:build

This sequence executes yarn mask (for prompt template compilation) followed by yarn tauri build. The Tauri CLI reads src-tauri/tauri.conf.json and creates native bundles for Windows (.msi), macOS (.dmg), and Linux (.AppImage).

5. Locate Output Binaries

After a successful build, find the distributable files in src-tauri/target/release/bundle/:

  • Windows: src-tauri/target/release/bundle/msi/NextChat_2.16.1_x64_en-US.msi
  • macOS: src-tauri/target/release/bundle/dmg/NextChat_2.16.1_x64.dmg
  • Linux: src-tauri/target/release/bundle/appimage/NextChat_2.16.1_amd64.AppImage

Key Implementation Files

Understanding these core files helps when customizing the desktop application or extending its native capabilities.

Tauri Configuration

The src-tauri/tauri.conf.json file controls the build pipeline and security settings. It defines beforeBuildCommand as yarn export, sets the distDir to ../out, and configures the allowlist for HTTP requests, file system access, and notifications.

{
  "build": {
    "beforeBuildCommand": "yarn export",
    "beforeDevCommand": "yarn export:dev",
    "devPath": "http://localhost:3000",
    "distDir": "../out"
  },
  "tauri": {
    "allowlist": {
      "http": { "all": true, "request": true, "scope": ["https://*", "http://*"] }
    },
    "bundle": {
      "category": "DeveloperTool",
      "icon": ["icons/32x32.png", "icons/128x128.png", "icons/icon.icns"]
    }
  }
}

The stream_fetch Rust Command

Located in src-tauri/src/stream.rs, this command handles HTTP requests natively using the reqwest crate. It accepts method, URL, headers, and body parameters, then streams the response back to the frontend via Tauri events.

#[tauri::command]
pub async fn stream_fetch(
    window: tauri::Window,
    method: String,
    url: String,
    headers: HashMap<String, String>,
    body: Vec<u8>,
) -> Result<StreamResponse, String> {
    let client = reqwest::Client::new();
    let response = client.request(method.parse()?, url.parse()?)
        .headers(convert_headers(headers)?)
        .body(body)
        .send()
        .await;

    match response {
        Ok(res) => {
            let request_id = generate_id();
            let status = res.status().as_u16();
            
            // Stream chunks back to the webview
            tauri::async_runtime::spawn(async move {
                let mut stream = res.bytes_stream();
                while let Some(chunk) = stream.next().await {
                    if let Ok(bytes) = chunk {
                        window.emit("stream-response", ChunkPayload {
                            request_id: request_id.clone(),
                            chunk: bytes.to_vec()
                        }).ok();
                    }
                }
                window.emit("stream-response", EndPayload {
                    request_id: request_id.clone(),
                    status: 0
                }).ok();
            });

            Ok(StreamResponse {
                request_id,
                status,
                status_text: "OK".into(),
                headers: extract_headers(&res),
            })
        }
        Err(err) => Err(err.to_string()),
    }
}

The TypeScript Bridge

The app/utils/stream.ts file provides a drop-in replacement for the standard fetch API. When window.__TAURI__ is present, it invokes stream_fetch and reconstructs a web-standard Response object from the streamed chunks.

if (window.__TAURI__) {
  const { method = "GET", headers = {}, body } = options || {};
  
  return window.__TAURI__.invoke("stream_fetch", {
    method: method.toUpperCase(),
    url,
    headers,
    body: typeof body === "string" 
      ? Array.from(new TextEncoder().encode(body)) 
      : [],
  }).then((res: StreamResponse) => {
    const { request_id, status, status_text, headers } = res;
    setRequestId?.(request_id);
    
    // Reassemble streaming chunks into a Response
    return new Response(readableStream, { 
      status, 
      statusText: status_text, 
      headers 
    });
  });
}

Summary

  • NextChat uses Tauri to wrap the Next.js frontend as a native desktop application for Windows, macOS, and Linux.
  • The stream_fetch Rust command in src-tauri/src/stream.rs bypasses browser CORS limitations by executing HTTP requests in the native runtime.
  • Run yarn app:dev to launch the application in development mode with hot-reload support on localhost:3000.
  • Execute yarn app:build to generate production binaries including .msi, .dmg, and .AppImage packages in src-tauri/target/release/bundle/.
  • Customize window behavior, security policies, and branding by editing src-tauri/tauri.conf.json.

Frequently Asked Questions

What is Tauri and why does NextChat use it?

Tauri is a Rust-based framework for building desktop applications using web technologies. NextChat uses Tauri to package the existing Next.js web interface as a native application without rewriting the frontend. This approach provides native performance, smaller bundle sizes than Electron, and full access to system APIs while maintaining the web-based codebase.

How does the stream_fetch command handle HTTP requests?

The stream_fetch command executes HTTP requests using Rust's reqwest library instead of the browser's fetch API. It accepts request parameters from the frontend, sends the request from the native Rust context, and streams response chunks back via Tauri events. This design allows the application to bypass CORS restrictions and handle large streaming responses from LLM APIs efficiently.

Where are the compiled desktop binaries located?

Production builds appear in src-tauri/target/release/bundle/ under platform-specific subdirectories. Windows installers use the msi/ folder, macOS disk images are in dmg/, and Linux AppImage files are located in appimage/. The filenames follow the pattern NextChat_[version]_[arch].[ext] (for example, NextChat_2.16.1_x64_en-US.msi).

Can I customize the desktop app branding and window settings?

Yes, modify src-tauri/tauri.conf.json to change the product name, icons, window dimensions, and security policies. The package.productName field controls the application title, while the tauri.bundle section configures installer metadata and icon paths. For advanced customizations such as native menu bars or tray icons, extend the Rust code in src-tauri/src/main.rs.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →