Jesse A. Green / notebook

Live demo: the DocCompiler guide library as a static, read-only snapshot (October 2026). No backend, no accounts, nothing to submit.

NextChat (ChatGPT-Next-Web) — Administration & Architecture Guide

Generated May 2026 from ChatGPTNextWeb/NextChat at c3b8c15

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

NextChat is a cross-platform AI chat interface that connects to 16 LLM providers through a unified proxy architecture. Built on Next.js 14 with React 18, TypeScript, SCSS Modules, and Zustand state management, it ships as both a web application and a Tauri-based desktop application with a Rust backend. This guide covers every aspect an administrator, operator, or contributing developer needs to deploy, configure, secure, and maintain a NextChat instance.


1. Architecture Overview

1.1 Technology Stack

Layer Technology Version
Framework Next.js (App Router) 14
UI Library React 18
Styling SCSS Modules —
State Management Zustand (persisted) —
Client Storage IndexedDB (idb-keyval), localStorage —
Desktop Runtime Tauri 1.5
Desktop Backend Rust —
Bundler Webpack 5
Language TypeScript 5.2
Testing Jest, Testing Library 29
Linting ESLint, Prettier, Husky, lint-staged —
Package Manager Yarn 1.22

1.2 High-Level Component Map

Browser / Tauri Shell
│
├── Next.js App Router (app/)
│   ├── layout.tsx              Root layout, metadata, analytics injection
│   ├── page.tsx                Entry point → Home component
│   └── API Routes (app/api/)
│       ├── [provider]/[...path]/route.ts   Unified provider proxy
│       ├── config/route.ts                 Server-side config endpoint
│       ├── artifacts/route.ts              HTML artifact sharing
│       ├── webdav/[...path]/route.ts       WebDAV sync proxy
│       ├── upstash/[action]/[...key]/route.ts  Upstash sync proxy
│       └── tencent/route.ts                Tencent-specific signing
│
├── Client Layer (app/client/)
│   ├── api.ts            ClientApi — provider routing dispatcher
│   ├── controller.ts     ChatControllerPool — abort/lifecycle
│   └── platforms/        14 provider implementations
│
├── State Stores (app/store/)   9 Zustand persisted stores
├── MCP Layer (app/mcp/)        Model Context Protocol integration
├── Components (app/components/)
├── Config (app/config/)        Server + client + build config
├── Locales (app/locales/)      20 language files
├── Masks (app/masks/)          Built-in persona definitions
├── Utils (app/utils/)          19 utility modules
│
└── Tauri Desktop (src-tauri/)
    ├── src/main.rs             Tauri entry, stream_fetch command
    ├── src/stream.rs           Streaming HTTP bridge
    ├── Cargo.toml              Rust dependencies
    └── tauri.conf.json         Desktop app configuration

1.3 Request Data Flow

A chat message traverses the following path from user input to stored response:

  1. Input — User types a message. useChatStore.onUserInput() is called.
  2. Template expansion — The input template (DEFAULT_INPUT_TEMPLATE or user-configured) is applied via fillTemplateWith(). Available variables: {{input}}, {{time}}, {{model}}, {{lang}}, {{cutoff}}, {{ServiceProvider}}.
  3. Context assembly — The system prompt, long-term memory summary, mask (persona) context messages, and recent chat history are concatenated into a messages array. Message count is bounded by the configured context window and CHAT_PAGE_SIZE (15) / MAX_RENDER_MSG_COUNT (45).
  4. Provider dispatch — getClientApi(providerName) resolves the correct platform client from app/client/platforms/.
  5. Streaming — The provider's chat() method sends the request through the Next.js API proxy (or directly in Tauri mode) and streams the response via Server-Sent Events (SSE).
  6. Persistence — On stream completion the message is saved to the active session in useChatStore. If the session's message count exceeds the summarize threshold, an automatic summarization request fires in the background.
  7. MCP execution (optional) — When ENABLE_MCP=true, assistant responses are scanned for fenced blocks matching the pattern ```json:mcp:{clientId}```. Matched blocks are parsed and executed via executeMcpAction() server actions.

1.4 API Proxy Architecture

All provider API calls are routed through Next.js API routes at /api/[provider]/[...path]. Every route follows the same pipeline:

  1. Authentication — auth() (defined in app/api/auth.ts) validates either a hashed access code or a provider-specific API key from the request headers.
  2. Model gating — isModelNotavailableInServer() checks whether the requested model is blocked by server configuration (e.g., DISABLE_GPT4 or CUSTOM_MODELS exclusions).
  3. Proxying — The request is forwarded to the provider's base URL (default or custom via *_URL env var) with appropriate headers.
  4. Streaming — The provider's response is streamed back to the client with CORS headers attached.

Provider-specific handler modules (app/api/openai.ts, app/api/anthropic.ts, app/api/google.ts, etc.) implement the auth-check-proxy pattern with provider-specific header transformations and request body adjustments.

NOTE: The proxy architecture means the server holds all provider API keys. Client-side keys stored in the browser are sent as request headers and used as a fallback when no server-side key is configured for a given provider.

1.5 Zustand Store Architecture

All persistent client-side state lives in nine Zustand stores, each created via createPersistStore() with version-stamped migrations.

Store localStorage Key Schema Version Responsibility
useAccessStore access-control 2 Auth state, provider API keys, cached server config
useChatStore chat-next-web-store 3.3 Chat sessions, messages, MCP tool-call state
useAppConfig app-config 4.1 UI preferences, default model config
useMaskStore mask-store 3.1 Custom personas (masks)
usePluginStore chat-next-web-plugin 1 OpenAPI-based plugins
usePromptStore prompt-store 3 User prompt library
useSdStore sd-list 1.0 Stable Diffusion image generation state
useSyncStore sync 1.2 WebDAV / Upstash sync configuration
useUpdateStore chat-update 1 Version-check metadata

Data is serialized to IndexedDB (via idb-keyval) for large payloads (chat history) and localStorage for smaller config stores. The IndexedDBStorage class in app/utils/indexedDB-storage.ts wraps idb-keyval and provides the Zustand persist adapter.

IMPORTANT: Store schema versions are critical for migration correctness. When modifying store shapes in development, always increment the version and add a migration function — otherwise users upgrading in place will encounter silent data corruption.


2. System Requirements

2.1 Server / Build Host

Resource Minimum Recommended
Node.js 18.x 20.x LTS
Yarn 1.22+ 1.22+
RAM (build) 2 GB 4 GB
RAM (runtime) 512 MB 1 GB
Disk 500 MB (source + deps) 1 GB

For the Tauri desktop build, additionally:

Resource Requirement
Rust toolchain stable (rustup)
Tauri CLI @tauri-apps/cli v1.5
OS-specific deps See Tauri prerequisites for Linux (webkit2gtk, etc.), macOS (Xcode CLT), or Windows (WebView2, VS Build Tools)

2.2 Runtime Environment

NextChat runs as a standard Next.js application. It requires:

  • A Node.js 18+ runtime (or a Docker container providing one).
  • Network access to at least one LLM provider API endpoint.
  • A reverse proxy (nginx, Traefik, Cloudflare Tunnel, etc.) for TLS termination in production.

2.3 Provider Requirements

At least one provider API key must be configured. No provider is mandatory — the application is provider-agnostic by design.


3. Configuration Reference

All server-side configuration is read from environment variables by getServerSideConfig() in app/config/server.ts. There are no configuration files on the server side; everything is env-var driven. The variables are grouped below by functional area.

3.1 Core Application Settings

Variable Type Default Description
CODE string (none) Comma-separated access codes. Each code is MD5-hashed (via spark-md5) before comparison. Users must enter one of these codes to access the instance. Leave empty to allow unrestricted access.
BASE_URL string (none) Custom base URL for the OpenAI-compatible endpoint. Overrides the default https://api.openai.com.
PROXY_URL string (none) HTTP proxy URL for outbound API requests. Only effective inside Docker or server environments where the Node.js process honors HTTP_PROXY-style variables.
HIDE_USER_API_KEY string (none) Set to any non-empty value to hide the API key input fields in the client UI. Useful when you want to enforce server-side keys only.
DISABLE_GPT4 string (none) Set to any non-empty value to block all GPT-4 class models from selection. Applies server-side — requests for GPT-4 models are rejected at the proxy.
ENABLE_BALANCE_QUERY string (none) Set to any non-empty value to expose a balance-check endpoint in the UI (OpenAI billing API).
DISABLE_FAST_LINK string (none) Disables URL fragment-based settings import (# links that auto-configure provider keys). Set this to prevent users from being phished via crafted URLs.
CUSTOM_MODELS string (none) Comma-separated model list. Prefix a model name with - to remove it from the default list. Example: +my-custom-model,-gpt-4 adds one model and removes another.
DEFAULT_MODEL string (none) Model ID to pre-select for new chat sessions. Must match an available model name exactly.
VISION_MODELS string (none) Comma-separated list of model names that support image/vision input. Enables the image-upload button for these models.
DEFAULT_INPUT_TEMPLATE string {{input}} Template applied to every user message before sending. Supports variables: {{input}}, {{time}}, {{model}}, {{lang}}, {{cutoff}}, {{ServiceProvider}}.
ENABLE_MCP string (none) Set to "true" (string) to enable Model Context Protocol server-side actions. Requires MCP server configuration in app/mcp/mcp_config.json.
WHITE_WEBDAV_ENDPOINTS string (none) Comma-separated list of additional allowed WebDAV endpoint domains. The sync proxy validates destination URLs against a built-in whitelist plus these additions.

CAUTION: Setting CODE to an empty string or omitting it entirely disables access control. Any visitor can use the instance and consume your API quota. Always set access codes in production.

3.2 Build Settings

Variable Type Default Description
BUILD_MODE string standalone standalone for standard Next.js server output. export for static HTML export (used by Tauri desktop builds).
BUILD_APP string (none) Set to any value during desktop app builds. Triggers Tauri-specific build paths.
VERCEL string (none) Auto-set by the Vercel platform. Detected by the build system to adjust output settings.

3.3 Provider API Keys

Every provider supports multi-key rotation: supply multiple keys separated by commas, and the client will randomly select one per request to distribute rate-limit pressure.

OpenAI

Variable Description
OPENAI_API_KEY OpenAI API key(s). Comma-separated for rotation.
OPENAI_ORG_ID OpenAI organization ID. Sent as OpenAI-Organization header.

Azure OpenAI

Variable Description
AZURE_URL Azure OpenAI resource endpoint (e.g., https://myresource.openai.azure.com).
AZURE_API_KEY Azure API key(s).
AZURE_API_VERSION Azure API version string (e.g., 2024-02-01).

Google (Gemini)

Variable Description
GOOGLE_API_KEY Google AI / Gemini API key(s).
GOOGLE_URL Custom Gemini API endpoint.

Anthropic (Claude)

Variable Description
ANTHROPIC_API_KEY Anthropic API key(s).
ANTHROPIC_URL Custom Anthropic API endpoint.
ANTHROPIC_API_VERSION Anthropic API version string.

Baidu (Ernie)

Variable Description
BAIDU_API_KEY Baidu API key(s).
BAIDU_URL Custom Baidu endpoint.
BAIDU_SECRET_KEY Baidu OAuth secret key for token exchange.

ByteDance (Doubao)

Variable Description
BYTEDANCE_API_KEY ByteDance API key(s).
BYTEDANCE_URL Custom ByteDance endpoint.

Alibaba (Qwen)

Variable Description
ALIBABA_API_KEY Alibaba / DashScope API key(s).
ALIBABA_URL Custom Alibaba endpoint.

Tencent (Hunyuan)

Variable Description
TENCENT_SECRET_KEY Tencent Cloud secret key.
TENCENT_SECRET_ID Tencent Cloud secret ID.
TENCENT_URL Custom Tencent Hunyuan endpoint.

Moonshot

Variable Description
MOONSHOT_API_KEY Moonshot API key(s).
MOONSHOT_URL Custom Moonshot endpoint.

iFlytek (Spark)

Variable Description
IFLYTEK_API_KEY iFlytek API key.
IFLYTEK_API_SECRET iFlytek API secret.
IFLYTEK_URL Custom iFlytek endpoint.

DeepSeek

Variable Description
DEEPSEEK_API_KEY DeepSeek API key(s).
DEEPSEEK_URL Custom DeepSeek endpoint.

xAI (Grok)

Variable Description
XAI_API_KEY xAI API key(s).
XAI_URL Custom xAI endpoint.

ChatGLM

Variable Description
CHATGLM_API_KEY ChatGLM (Zhipu AI) API key(s).
CHATGLM_URL Custom ChatGLM endpoint.

SiliconFlow

Variable Description
SILICONFLOW_API_KEY SiliconFlow API key(s).
SILICONFLOW_URL Custom SiliconFlow endpoint.

302.AI

Variable Description
AI302_API_KEY 302.AI API key(s).
AI302_URL Custom 302.AI endpoint.

Stability AI (Image Generation)

Variable Description
STABILITY_API_KEY Stability AI API key for Stable Diffusion image generation.
STABILITY_URL Custom Stability AI endpoint.

3.4 Cloudflare KV (Optional Caching)

Variable Description
CLOUDFLARE_ACCOUNT_ID Cloudflare account identifier.
CLOUDFLARE_KV_NAMESPACE_ID KV namespace to use for caching.
CLOUDFLARE_KV_API_KEY API key with KV read/write permissions.
CLOUDFLARE_KV_TTL Cache time-to-live in seconds.

3.5 Analytics

Variable Default Description
GTM_ID (none) Google Tag Manager container ID.
GA_ID G-89WN60ZK2E Google Analytics measurement ID. The default tracks usage for the upstream project maintainers. Override or unset this for self-hosted instances.

TIP: To disable all analytics on a self-hosted instance, set GA_ID to an empty string and leave GTM_ID unset.

3.6 Complete Variable Count

The extraction identifies 77 environment variables across the categories above. Every variable is read in app/config/server.ts via process.env — there is no .env file parsing library; the runtime environment (Node.js, Docker, Vercel) must inject them.


4. Deployment & Scaling

NextChat ships with a Dockerfile and Docker Compose configuration at the repository root.

# Build and run
docker build -t nextchat .
docker run -d \
  -p 3000:3000 \
  -e CODE="your-access-code" \
  -e OPENAI_API_KEY="sk-..." \
  --name nextchat \
  nextchat

For multi-provider setups, pass all required *_API_KEY and *_URL variables:

docker run -d \
  -p 3000:3000 \
  -e CODE="admin123,user456" \
  -e OPENAI_API_KEY="sk-..." \
  -e ANTHROPIC_API_KEY="sk-ant-..." \
  -e GOOGLE_API_KEY="AIza..." \
  -e HIDE_USER_API_KEY=1 \
  -e DISABLE_GPT4=1 \
  -e CUSTOM_MODELS="-gpt-4-32k,-gpt-4o" \
  --name nextchat \
  nextchat

NOTE: The PROXY_URL variable is specifically designed for Docker deployments where outbound traffic must traverse an HTTP proxy. In non-Docker environments, use standard HTTP_PROXY / HTTPS_PROXY environment variables instead.

4.2 Docker Compose

version: "3"
services:
  nextchat:
    build: .
    ports:
      - "3000:3000"
    environment:
      - CODE=your-access-code
      - OPENAI_API_KEY=sk-...
    restart: unless-stopped

4.3 Vercel Deployment

NextChat is designed for one-click Vercel deployment:

  1. Fork or import the repository into Vercel.
  2. Set environment variables in the Vercel dashboard (Settings → Environment Variables).
  3. Deploy. The VERCEL env var is auto-set and triggers appropriate build adjustments.

Vercel's edge network handles TLS, CDN, and scaling automatically. The BUILD_MODE=standalone default works correctly on Vercel.

4.4 Static Export (Tauri Desktop)

For the desktop application:

yarn export        # Builds static HTML export
yarn app:build     # Triggers Tauri to package the desktop app

The BUILD_MODE=export and BUILD_APP=1 variables are set automatically by the export script. The Tauri build produces platform-specific installers (.dmg, .msi, .AppImage/.deb).

4.5 Manual Node.js Deployment

yarn install
yarn build
yarn start          # Starts on port 3000

Place behind a reverse proxy (nginx, Caddy, Traefik) for TLS termination.

4.6 Scaling Considerations

NextChat is a stateless server — all persistent state lives in the client's browser (IndexedDB/localStorage). This makes horizontal scaling straightforward:

  • Multiple instances behind a load balancer require no session affinity.
  • No shared database — the server reads only environment variables and proxies requests.
  • Bottleneck — the upstream LLM provider APIs, not NextChat itself. Rate limits are per-API-key; use multi-key rotation (comma-separated keys) to distribute load.

For high-traffic deployments, scale by:

  1. Running multiple container replicas behind a load balancer.
  2. Using separate API keys per replica (or comma-separated pools).
  3. Deploying to edge platforms (Vercel, Cloudflare Workers) for geographic distribution.

NOTE: The WebDAV and Upstash sync endpoints are server-proxied. If sync traffic is high, ensure the proxy routes have adequate timeout settings (the default REQUEST_TIMEOUT_MS is 60 seconds).


5. Operations

5.1 Build Scripts Reference

Script Command Purpose
yarn dev concurrently "yarn mask:watch" "next dev" Development server with hot reload and mask file watching
yarn build yarn mask && cross-env BUILD_MODE=standalone next build Production build (standalone Next.js output)
yarn start next start Start the production server
yarn export yarn mask && cross-env BUILD_MODE=export BUILD_APP=1 next build Static HTML export for Tauri desktop packaging
yarn app:dev concurrently "yarn mask:watch" "yarn tauri dev" Desktop app development with hot reload
yarn app:build yarn mask && yarn tauri build Production desktop app build
yarn test jest --watch Run tests in watch mode
yarn test:ci jest --ci CI test run (single pass, no watch)
yarn mask npx tsx app/masks/build.ts Compile mask (persona) definitions from source
yarn prompts node ./scripts/fetch-prompts.mjs Fetch community prompt library

5.2 Health Monitoring

NextChat exposes a /api/config endpoint that returns the server-side configuration (sanitized — no API keys). This can serve as a basic health check endpoint.

Key metrics to monitor in production:

  • HTTP 429 responses from upstream providers (rate limiting).
  • SSE stream disconnections — usually caused by proxy timeout settings being too low.
  • Memory usage — the Next.js server process should remain under 512 MB for typical usage.
  • Error rates on /api/[provider]/[...path] — provider-specific failures.

5.3 Sync Backends

NextChat supports two server-mediated sync backends for cross-device data portability:

WebDAV Sync:

  • Client configures a WebDAV server URL, username, and password in Settings → Sync.
  • All sync traffic is proxied through /api/webdav/[...path] to avoid CORS issues.
  • The WHITE_WEBDAV_ENDPOINTS env var controls which destination domains are allowed.

Upstash (Redis) Sync:

  • Client configures an Upstash REST endpoint and token.
  • Proxied through /api/upstash/[action]/[...key].
  • Provides faster sync for users who prefer a managed Redis backend.

TIP: For enterprise deployments, host your own WebDAV server (e.g., with nginx + dav_ext_module) and whitelist only that endpoint. This keeps all user data within your infrastructure.

5.4 Logging and Debugging

NextChat does not implement a structured logging framework. Operational visibility comes from:

  • Browser console — client-side errors, network failures, store hydration issues.
  • Node.js stdout/stderr — server-side API route errors, proxy failures.
  • MCP Logger — The MCPClientLogger class (app/mcp/logger.ts) provides color-coded console output for MCP server lifecycle events.
  • Error boundaries — The ErrorBoundary component (app/components/error.tsx) catches React rendering errors and displays a fallback UI with error details.

For production Docker deployments, ensure docker logs or a log aggregator captures stdout/stderr from the container.

5.5 Version Update Checking

The useUpdateStore periodically checks for new releases by fetching from the GitHub releases API. The check interval and behavior are controlled client-side. Administrators cannot disable this check server-side — it runs entirely in the browser.

5.6 Localization

NextChat supports 20 languages via locale files in app/locales/. The locale is auto-detected from the browser's navigator.language and can be overridden in Settings. Adding a new locale requires:

  1. Creating a new file in app/locales/ following the existing pattern.
  2. Registering it in the locale index.
  3. Rebuilding.

6. Performance

6.1 Key Constants and Timeouts

Constant Value Location Purpose
REQUEST_TIMEOUT_MS 60,000 ms (60s) Client constants Default timeout for API requests
REQUEST_TIMEOUT_MS_FOR_THINKING 300,000 ms (5min) Client constants Extended timeout for DALL-E, o1, o3, DeepSeek-R, and other "thinking" models
CHAT_PAGE_SIZE 15 Client constants Messages loaded per pagination page
MAX_RENDER_MSG_COUNT 45 Client constants Maximum messages rendered simultaneously in the chat view
DEFAULT_SIDEBAR_WIDTH 300px Client constants Initial sidebar width
MAX_SIDEBAR_WIDTH 500px Client constants Maximum sidebar width
MIN_SIDEBAR_WIDTH 230px Client constants Minimum sidebar width
MOBILE_MAX_WIDTH 600px Client constants Breakpoint for mobile layout

6.2 Client-Side Performance

  • Virtualized rendering — Only MAX_RENDER_MSG_COUNT (45) messages are rendered in the DOM at once, regardless of conversation length.
  • Lazy markdown parsing — The markdown.tsx component defers heavy parsing (syntax highlighting, LaTeX rendering) until the message is visible.
  • IndexedDB for large data — Chat history is stored in IndexedDB rather than localStorage to avoid the 5-10 MB localStorage limit and reduce main-thread serialization overhead.
  • Abort handling — ChatControllerPool (app/client/controller.ts) tracks active SSE connections per session and provides abort functionality. Navigating away from a chat aborts its active stream.

6.3 Server-Side Performance

The Next.js server is I/O-bound (proxying HTTP requests). CPU usage is minimal. Key performance considerations:

  • Streaming — All provider responses are streamed via SSE, not buffered. Memory usage per concurrent request is bounded by the SSE chunk size, not the total response length.
  • No database — Zero database queries per request. The server is purely a proxy + config endpoint.
  • Cold starts — On serverless platforms (Vercel), cold starts add 1-3 seconds to the first request. Subsequent requests are fast.

TIP: For self-hosted deployments behind nginx, set proxy_buffering off and increase proxy_read_timeout to at least 300 seconds to support long-running streaming responses from thinking models.


7. Security

7.1 Authentication Model

NextChat uses a simple access-code authentication system:

  1. The administrator sets one or more access codes via the CODE environment variable (comma-separated).
  2. Each code is MD5-hashed using the spark-md5 library before storage/comparison.
  3. Users enter an access code in the Settings panel. The code is hashed client-side and stored in useAccessStore (localStorage key: access-control).
  4. On every API proxy request, auth() in app/api/auth.ts checks either:
    • The hashed access code in the Authorization header (prefixed with ACCESS_CODE_PREFIX = "nk-"), OR
    • A provider-specific API key in the appropriate header.
  5. If neither is valid, the request is rejected.

WARNING: The MD5 hashing of access codes provides only obfuscation, not cryptographic security. MD5 is fast to brute-force. For production deployments with sensitive API keys, place NextChat behind an additional authentication layer (OAuth proxy, VPN, IP allowlist) rather than relying solely on access codes.

7.2 Known Security Vulnerabilities

The following security issues have been reported in GitHub issues and should be evaluated before deploying to production:

Issue Severity Description
#6756 Medium CORS wildcard (*) is set on all API endpoints. This allows any origin to make authenticated requests if the user's access code is known.
#6754 / #6742 High SSRF vulnerability via the x-base-url header in the proxy fallback path. An attacker can make the server issue requests to internal network addresses.
#6753 / #6741 High Path traversal in the artifacts endpoint (/api/artifacts). Crafted paths can leak server-side secrets including Cloudflare API tokens.
#6757 Critical Unauthenticated Remote Code Execution report. Details vary by version — review the issue for applicability to your deployment.

WARNING: These vulnerabilities are architectural and may persist across versions. Before deploying NextChat in any environment with access to sensitive internal networks or valuable API keys, audit the specific version you are deploying against these CVE-equivalent issues. Consider network-level isolation (dedicated VPC, firewall rules blocking internal access from the NextChat container) as defense-in-depth.

7.3 API Key Security

  • Server-side keys are read from environment variables and never sent to the client. They are injected into proxied requests server-side.
  • Client-side keys (when HIDE_USER_API_KEY is not set) are stored in the browser's localStorage in plaintext. Any XSS vulnerability would expose them.
  • Multi-key rotation reduces the blast radius of a single key compromise — rotate the compromised key without downtime.

IMPORTANT: Never commit API keys to the repository or Dockerfile. Use environment variables, Docker secrets, or your platform's secret management (Vercel Environment Variables, Kubernetes Secrets, etc.).

When DISABLE_FAST_LINK is not set, NextChat parses URL fragments for settings import. A crafted URL can pre-fill provider keys and other settings when a user clicks it. This is a social-engineering vector — an attacker could distribute URLs that configure the victim's instance to proxy through the attacker's endpoint.

Set DISABLE_FAST_LINK=1 in production to close this vector.

7.5 WebDAV Proxy Security

The /api/webdav/[...path] route proxies requests to user-specified WebDAV servers. The WHITE_WEBDAV_ENDPOINTS variable restricts allowed destinations, but:

  • The default whitelist may be overly permissive depending on the version.
  • SSRF is possible if internal hostnames resolve within the server's network.

Restrict WHITE_WEBDAV_ENDPOINTS to known-good domains and block internal network ranges at the firewall level.

7.6 Security Hardening Checklist

  1. Set CODE with strong, unique access codes.
  2. Set HIDE_USER_API_KEY=1 to prevent client-side key leakage.
  3. Set DISABLE_FAST_LINK=1 to block URL-based configuration injection.
  4. Set DISABLE_GPT4=1 if you want to limit cost exposure.
  5. Override GA_ID with an empty string to stop analytics data from flowing to third parties.
  6. Deploy behind a reverse proxy with TLS termination.
  7. Network-isolate the NextChat container — block outbound access to internal IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) except for legitimate internal services.
  8. Set WHITE_WEBDAV_ENDPOINTS to only your approved sync endpoints.
  9. Restrict CUSTOM_MODELS to only the models you intend to offer (remove expensive or unnecessary models with - prefix).
  10. Run the container as a non-root user.

8. Developer Internals

8.1 Repository Structure

NextChat/
├── app/                         # Next.js App Router source
│   ├── api/                     # Server-side API routes
│   │   ├── auth.ts              # Access code + API key validation
│   │   ├── openai.ts            # OpenAI provider handler
│   │   ├── anthropic.ts         # Anthropic provider handler
│   │   ├── google.ts            # Google/Gemini provider handler
│   │   ├── tencent/route.ts     # Tencent signing + proxy
│   │   ├── config/route.ts      # Server config endpoint
│   │   ├── artifacts/route.ts   # HTML artifact serving
│   │   ├── webdav/              # WebDAV sync proxy
│   │   ├── upstash/             # Upstash sync proxy
│   │   └── [provider]/          # Unified catch-all proxy
│   ├── client/                  # Client-side API layer
│   │   ├── api.ts               # ClientApi dispatcher class
│   │   ├── controller.ts        # ChatControllerPool (stream lifecycle)
│   │   └── platforms/           # 14 provider implementations
│   │       ├── openai.ts
│   │       ├── anthropic.ts
│   │       ├── google.ts
│   │       └── ... (11 more)
│   ├── components/              # React UI components
│   │   ├── home.tsx             # Main layout + router
│   │   ├── chat.tsx             # Chat interface
│   │   ├── settings.tsx         # Settings panel
│   │   ├── sidebar.tsx          # Navigation sidebar
│   │   ├── mask.tsx             # Persona/mask editor
│   │   ├── new-chat.tsx         # New chat creation
│   │   ├── plugin.tsx           # Plugin management
│   │   ├── artifacts.tsx        # HTML artifact renderer (iframe sandbox)
│   │   ├── exporter.tsx         # Chat export (Markdown, PNG, ShareGPT)
│   │   ├── markdown.tsx         # Markdown renderer (highlight.js, KaTeX)
│   │   ├── mcp-market.tsx       # MCP server marketplace
│   │   ├── model-config.tsx     # Model parameter controls
│   │   ├── error.tsx            # ErrorBoundary component
│   │   ├── ui-lib.tsx           # Shared UI primitives
│   │   ├── realtime-chat/       # Real-time voice chat components
│   │   ├── sd/                  # Stable Diffusion UI
│   │   └── voice-print/        # Voice visualization
│   ├── config/                  # Configuration modules
│   │   ├── server.ts            # getServerSideConfig() — all 77 env vars
│   │   ├── client.ts            # getClientConfig() — build metadata
│   │   └── build.ts             # getBuildConfig() — version, commit hash
│   ├── store/                   # Zustand persisted stores
│   │   ├── access.ts            # Auth, provider keys, server config cache
│   │   ├── chat.ts              # Sessions, messages, MCP tool calls
│   │   ├── config.ts            # UI + model settings
│   │   ├── mask.ts              # Persona management
│   │   ├── plugin.ts            # OpenAPI plugin state
│   │   ├── prompt.ts            # User prompt library
│   │   ├── sd.ts                # Stable Diffusion state
│   │   ├── sync.ts              # Sync configuration
│   │   └── update.ts            # Version checking
│   ├── mcp/                     # Model Context Protocol
│   │   ├── actions.ts           # Server actions (start/stop/call)
│   │   ├── client.ts            # MCP SDK client wrapper
│   │   ├── logger.ts            # MCPClientLogger
│   │   ├── types.ts             # Zod schemas for MCP
│   │   ├── utils.ts             # JSON extraction from chat messages
│   │   └── mcp_config.default.json
│   ├── locales/                 # 20 language translation files
│   ├── masks/                   # Built-in persona definitions + build script
│   ├── utils/                   # 19 utility modules
│   │   ├── indexedDB-storage.ts # IndexedDBStorage class
│   │   ├── ms_edge_tts.ts       # Edge TTS client (MsEdgeTTS class)
│   │   ├── format.ts            # Text formatting utilities
│   │   ├── model.ts             # Model capability detection
│   │   ├── token.ts             # Token counting
│   │   └── ... (14 more)
│   ├── layout.tsx               # Root layout (meta, fonts, analytics)
│   └── page.tsx                 # Entry: renders Home component
├── src-tauri/                   # Tauri desktop app (Rust)
│   ├── src/
│   │   ├── main.rs              # Tauri entry, registers stream_fetch
│   │   └── stream.rs            # StreamResponse, ChunkPayload, EndPayload
│   ├── Cargo.toml               # Rust deps (reqwest, tauri, tokio, etc.)
│   └── tauri.conf.json          # Window config, bundle identifiers
├── public/                      # Static assets (icons, robots.txt)
├── scripts/                     # Build/fetch scripts
│   └── fetch-prompts.mjs        # Community prompt fetcher
├── Dockerfile                   # Container build definition
├── docker-compose.yml           # Compose configuration
├── package.json                 # Dependencies, scripts
├── tsconfig.json                # TypeScript configuration
├── next.config.mjs              # Next.js configuration
├── jest.config.ts               # Jest test configuration
├── .eslintrc.json               # ESLint rules
├── .prettierrc                  # Prettier formatting rules
└── .husky/                      # Git hooks (pre-commit lint-staged)

8.2 Class Reference

Class File Purpose
ClientApi app/client/api.ts Top-level API dispatcher. Routes requests to the correct provider platform based on the model's provider field.
ChatControllerPool app/client/controller.ts Manages active SSE connections per chat session. Provides stop() and stopAll() for abort control.
IndexedDBStorage app/utils/indexedDB-storage.ts Zustand persist adapter wrapping idb-keyval. Implements getItem(), setItem(), removeItem() for the persist middleware.
MsEdgeTTS app/utils/ms_edge_tts.ts Client for Microsoft Edge's text-to-speech WebSocket API. Handles SSML generation, WebSocket connection, and audio chunk reassembly.
ProsodyOptions app/utils/ms_edge_tts.ts Configuration object for Edge TTS voice prosody (rate, pitch, volume).
MCPClientLogger app/mcp/logger.ts Colored console logger for MCP server lifecycle events. Prefixes output with client ID for multi-server debugging.
ErrorBoundary app/components/error.tsx React error boundary. Catches component tree errors and renders a fallback UI with stack trace and retry button.
AudioHandler app/lib/audio.ts Browser audio recording handler using MediaRecorder API. Manages start/stop/data lifecycle for voice input.
StreamResponse src-tauri/src/stream.rs Rust struct representing a streaming HTTP response in the Tauri IPC bridge.
ChunkPayload src-tauri/src/stream.rs Rust struct for individual stream data chunks sent via Tauri events.
EndPayload src-tauri/src/stream.rs Rust struct signaling stream completion or error via Tauri events.

8.3 Provider Platform Pattern

Each provider in app/client/platforms/ follows a consistent pattern:

export class SomeProviderApi implements LLMApi {
  // Extract headers (API key, version, org ID)
  private getHeaders(): Record<string, string> { ... }

  // Build the request body with provider-specific field mappings
  private formatMessages(messages: ChatMessage[]): ProviderMessage[] { ... }

  // Main chat method — streams SSE response
  async chat(options: ChatOptions): Promise<void> {
    // 1. Build request from options
    // 2. Call fetchEventSource or fetch with streaming
    // 3. Parse SSE chunks into delta text
    // 4. Call options.onUpdate(delta) per chunk
    // 5. Call options.onFinish(fullText) on completion
    // 6. Call options.onError(err) on failure
  }

  // Model listing (some providers support dynamic model discovery)
  async models(): Promise<LLMModel[]> { ... }
}

The ClientApi class wraps all providers and dispatches based on the ServiceProvider enum.

8.4 State Management Patterns

Store creation:

export const useSomeStore = createPersistStore(
  { /* default state */ },
  (set, get) => ({
    // actions
    someAction() {
      set({ field: newValue });
    },
  }),
  {
    name: "store-key",
    version: 1.0,
    migrate(state, version) {
      // version-based migration logic
      return state;
    },
  }
);

Persistence flow:

  1. Zustand's persist middleware serializes state to the configured storage backend.
  2. IndexedDBStorage is used for stores with large data (primarily useChatStore).
  3. Standard localStorage is used for smaller stores.
  4. On app load, stores hydrate from persisted data and run migrations if the version has changed.

8.5 MCP (Model Context Protocol) Integration

MCP enables the chat interface to invoke external tool servers:

  1. Configuration — MCP servers are defined in app/mcp/mcp_config.json (or the user's custom config). Each server specifies a command and args for StdioClientTransport.
  2. Lifecycle — Server actions in app/mcp/actions.ts manage the lifecycle: startMcpServer(), stopMcpServer(), listMcpTools(), callMcpTool().
  3. Server states — undefined → initializing → active (or error). Servers can be paused and resumed.
  4. Chat integration — When ENABLE_MCP=true, the assistant's responses are scanned for fenced JSON blocks tagged with json:mcp:{clientId}. The executeMcpAction() function parses these blocks and calls the corresponding MCP tool via callMcpTool().
  5. Supported methods — Only tools/call is implemented. Resource and prompt MCP capabilities are not used.
  6. Validation — MCP schemas are validated with Zod (app/mcp/types.ts).

NOTE: MCP server processes run on the Next.js server, not in the browser. This means MCP is unavailable in static export / Tauri desktop mode. It requires a persistent server process.

8.6 Plugin Architecture

NextChat supports OpenAPI-based plugins for extending assistant capabilities:

  1. Specification — Plugins are defined by an OpenAPI YAML/JSON spec URL. The openapi-client-axios library parses the spec and generates a type-safe client.
  2. Authentication — Three auth modes: basic (username/password), bearer (token), or custom (arbitrary header name/value).
  3. Execution modes:
    • Web mode — Plugin API calls are proxied through /api/proxy with the target URL in an X-Base-URL header. This avoids CORS issues but inherits the SSRF risk noted in the security section.
    • Tauri mode — Plugin API calls go directly from the Rust backend to the target server, bypassing CORS entirely.
  4. Built-in plugins — Loaded from /plugins.json in the public directory.
  5. State — Managed in usePluginStore (localStorage key: chat-next-web-plugin).

8.7 Error Messages Reference

Error Message Source File Context
Network response was not ok app/utils/ms_edge_tts.ts:263 Edge TTS WebSocket connection failure
Could not infer voiceLocale from voiceName! app/utils/ms_edge_tts.ts:296 Invalid voice name passed to TTS
Server ${clientId} not found app/mcp/actions.ts:201,237 MCP server referenced but not configured
Client ${clientId} not found app/mcp/actions.ts:344 MCP client instance not initialized
Failed to load preset servers app/components/mcp-market.tsx:99 MCP marketplace fetch failure
Failed to load tools app/components/mcp-market.tsx:235 MCP tool listing failure
Recording not started app/lib/audio.ts:88 Audio stop called before start
empty response Various provider clients LLM provider returned empty body

8.8 Testing Strategy

  • Framework: Jest 29 with Testing Library for component tests.
  • Configuration: jest.config.ts at root, TypeScript test files.
  • Commands: yarn test (watch mode for development), yarn test:ci (single pass for CI).
  • Lint enforcement: Husky pre-commit hooks run lint-staged (ESLint + Prettier on staged files).
  • Coverage gaps: The TypeScript extraction yielded 0 functions/actions/CLI commands due to the extraction tool's TypeScript parsing limitations. Test coverage should focus on:
    • Provider API handler request/response transformations.
    • Auth logic in app/api/auth.ts.
    • Store migration functions.
    • Markdown rendering edge cases.
    • MCP JSON block parsing in app/mcp/utils.ts.

8.9 Code Conventions

  • TypeScript strict mode — tsconfig.json enforces strict type checking.
  • SCSS Modules — Component styles are co-located as .module.scss files. No global CSS beyond minimal resets.
  • Functional React — All components are functional (hooks-based). The only class component is ErrorBoundary (required by React's error boundary API).
  • Named exports — Stores and components use named exports throughout.
  • Path aliases — @/ maps to the app/ directory in imports.
  • Constant naming — UPPER_SNAKE_CASE for module-level constants, camelCase for local variables and functions.
  • Enum usage — ServiceProvider enum maps provider names to their string identifiers across the codebase.

9. Integration Points

9.1 LLM Provider Integrations

NextChat integrates with 16 LLM providers through a unified proxy architecture. Each integration follows the same pattern (auth → model check → proxy → stream) but with provider-specific adaptations:

Provider Client Module API Route Protocol Notes
OpenAI platforms/openai.ts /api/openai/ REST + SSE Default provider. Chat + DALL-E image generation.
Azure OpenAI platforms/openai.ts /api/openai/ REST + SSE Shares OpenAI client with Azure-specific URL/key/version.
Anthropic platforms/anthropic.ts /api/anthropic/ REST + SSE Claude models. Different message format (system prompt handling).
Google Gemini platforms/google.ts /api/google/ REST + SSE Gemini models. Multimodal support (vision).
Baidu (Ernie) platforms/baidu.ts /api/baidu/ REST + SSE Requires OAuth token exchange with secret key.
ByteDance (Doubao) platforms/bytedance.ts /api/bytedance/ REST + SSE Volcengine ML platform models.
Alibaba (Qwen) platforms/alibaba.ts /api/alibaba/ REST + SSE DashScope API. Qwen models.
Tencent (Hunyuan) platforms/tencent.ts /api/tencent/ REST + SSE Requires HMAC signing (TC3-HMAC-SHA256). Dedicated route.
Moonshot platforms/moonshot.ts /api/moonshot/ REST + SSE OpenAI-compatible API.
iFlytek (Spark) platforms/iflytek.ts /api/iflytek/ REST + SSE Requires both API key and API secret.
DeepSeek platforms/deepseek.ts /api/deepseek/ REST + SSE OpenAI-compatible. Thinking models use extended timeout.
xAI (Grok) platforms/xai.ts /api/xai/ REST + SSE OpenAI-compatible API.
ChatGLM platforms/glm.ts /api/glm/ REST + SSE Zhipu AI platform.
SiliconFlow platforms/siliconflow.ts /api/siliconflow/ REST + SSE Multi-model inference platform.
302.AI platforms/ai302.ts /api/ai302/ REST + SSE Aggregator platform.
Stability AI platforms/stability.ts /api/stability/ REST Image generation only. Not chat.

9.2 Sync Integrations

Backend Protocol Proxy Route Configuration
WebDAV HTTP(S) /api/webdav/[...path] URL, username, password in useSyncStore
Upstash REST /api/upstash/[action]/[...key] Endpoint URL, token in useSyncStore

9.3 External Service Integrations

Service Purpose Configuration
Google Tag Manager Analytics GTM_ID env var
Google Analytics Usage tracking GA_ID env var (default tracks upstream project)
Cloudflare KV Response caching CLOUDFLARE_ACCOUNT_ID, CLOUDFLARE_KV_NAMESPACE_ID, CLOUDFLARE_KV_API_KEY, CLOUDFLARE_KV_TTL
Microsoft Edge TTS Text-to-speech No configuration — uses public WebSocket endpoint
GitHub Releases API Version update checking No configuration — hardcoded upstream repo
Community Prompts Prompt library Fetched via yarn prompts from external source

9.4 Tauri Desktop Integration

The Tauri layer bridges web and native functionality:

  • stream_fetch — A Tauri command registered in main.rs that performs HTTP requests via reqwest and streams responses back to the webview via Tauri events. This bypasses CORS restrictions entirely, allowing the desktop app to call provider APIs directly without the Next.js proxy.
  • StreamResponse / ChunkPayload / EndPayload — Rust structs in stream.rs that serialize stream data for the Tauri event system.
  • Window configuration — Defined in tauri.conf.json: title, dimensions, resizability, decorations, and CSP policy.
  • Build targets — macOS (.dmg, .app), Windows (.msi), Linux (.AppImage, .deb).

NOTE: In Tauri/desktop mode, MCP is unavailable (no server-side process), but CORS restrictions are absent. Plugin API calls go directly to target servers without the /api/proxy intermediary.

9.5 Extending NextChat

Adding a new LLM provider:

  1. Create app/client/platforms/newprovider.ts implementing the LLMApi interface.
  2. Add the provider to the ServiceProvider enum.
  3. Register it in ClientApi's provider map (app/client/api.ts).
  4. Create API route handler(s) in app/api/ following the auth-check-proxy pattern.
  5. Add environment variables for API key and optional custom URL to app/config/server.ts.
  6. Add UI strings to all locale files in app/locales/.

Adding a new Zustand store:

  1. Define the store using createPersistStore() with a unique key and version.
  2. Use IndexedDBStorage for stores expected to hold large data; localStorage otherwise.
  3. Always include a migrate() function, even if initially empty.

10. Troubleshooting

10.1 Common Issues

Symptom Likely Cause Resolution
"Unauthorized" on every request CODE is set but user hasn't entered a valid access code Verify the access code matches one of the comma-separated values in CODE. Codes are case-sensitive.
Streaming responses cut off mid-sentence Reverse proxy timeout too low Increase proxy_read_timeout (nginx) or equivalent to 300+ seconds.
"Failed to fetch" in browser console CORS or network issue Check that the Next.js server is accessible. If behind a proxy, ensure it forwards the Content-Type: text/event-stream header.
Chat history disappeared after upgrade Store schema migration failure Check browser console for migration errors. Clear localStorage key chat-next-web-store and reimport from backup (Settings → Backup).
MCP tools not appearing ENABLE_MCP not set or MCP config invalid Set ENABLE_MCP=true and verify app/mcp/mcp_config.json contains valid server definitions. Check server logs for MCP process startup errors.
Desktop app shows blank white screen Tauri WebView issue On Linux, ensure webkit2gtk is installed. On Windows, ensure WebView2 runtime is present.
Models missing from selection CUSTOM_MODELS filtering Check that CUSTOM_MODELS doesn't exclude the desired model with a - prefix.
Azure OpenAI not working Missing AZURE_API_VERSION Azure requires all three variables: AZURE_URL, AZURE_API_KEY, and AZURE_API_VERSION.
High memory usage in browser Large chat history in IndexedDB Export and delete old conversations. The MAX_RENDER_MSG_COUNT (45) limits DOM rendering but not storage.

TIP: For persistent issues, open the browser's developer console (F12) and check the Network tab for failed requests. The response body from failed API proxy calls often contains the upstream provider's error message.

NOTE: When running in Docker, use docker logs <container> to capture server-side errors. Add -f for real-time tailing during debugging sessions.

10.2 Environment Variable Debugging

The /api/config endpoint returns the server's parsed configuration (with API keys redacted). Fetch it to verify that environment variables are being read correctly:

curl http://localhost:3000/api/config | jq .

Check for:

  • needCode: true — confirms CODE is set.
  • Provider-specific isAvailable flags — confirms the corresponding API key is present.
  • customModels — confirms CUSTOM_MODELS is parsed correctly.

10.3 Store Reset Procedures

If a store becomes corrupted:

  1. Single store — In browser console: localStorage.removeItem("store-key") and refresh. For IndexedDB stores: use browser DevTools → Application → IndexedDB to delete the database.
  2. Full reset — Clear all site data via browser DevTools → Application → Clear Storage.
  3. Before resetting — Always export data first via Settings → Backup → Export to avoid data loss.