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.jsondeclares build commands, output directories, window settings, bundling options, and allowed APIs. - Rust Runtime:
src-tauri/src/main.rsinitializes the Tauri process, registers custom commands, and runs the event loop. - Custom Command:
src-tauri/src/stream.rsimplementsstream_fetch, a Rust-side HTTP client that streams LLM API responses while bypassing browser CORS restrictions. - TypeScript Bridge:
app/utils/stream.tsdetects the Tauri environment and substitutes standardfetchcalls 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.
- The UI calls
fetch(url, options), which detectswindow.__TAURI__and forwards the request viawindow.__TAURI__.invoke("stream_fetch", ...). - The Rust command in
src-tauri/src/stream.rscreates areqwest::Client, sends the request, and emits raw byte chunks back to the web view using the Tauri event"stream-response"withChunkPayloadstructs. - The TypeScript bridge listens for these events, writes each chunk into a
TransformStream, and resolves a standardResponseobject once the stream completes via anEndPayloadsignal.
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
rustupto compile the Tauri backend. - Tauri CLI: The
@tauri-apps/clipackage 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:3000as defined intauri.conf.json. - Tauri loads this URL directly instead of the static
outfolder, 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_fetchRust command insrc-tauri/src/stream.rsbypasses browser CORS limitations by executing HTTP requests in the native runtime. - Run
yarn app:devto launch the application in development mode with hot-reload support onlocalhost:3000. - Execute
yarn app:buildto generate production binaries including.msi,.dmg, and.AppImagepackages insrc-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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →