# How NextChat Synchronizes Chat History with WebDAV and Upstash

> Learn how NextChat synchronizes chat history using WebDAV and Upstash through a unified SyncClient. Discover how remote state is retrieved merged and pushed back to cloud providers.

- Repository: [NextChat/NextChat](https://github.com/ChatGPTNextWeb/NextChat)
- Tags: deep-dive
- Published: 2026-02-28

---

**NextChat synchronizes chat history by abstracting WebDAV and Upstash behind a unified `SyncClient` interface that retrieves remote state, merges it with local application data, and pushes updates back to the cloud provider.**

NextChat (ChatGPTNextWeb/NextChat) stores complete application state—including conversation history—in a local **AppState** object. When users enable cloud synchronization through *Settings → Sync*, the application can back up and restore this state using either WebDAV or Upstash Redis. Both providers implement the same minimal interface, allowing the sync logic in [`app/store/sync.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/store/sync.ts) to remain provider-agnostic while handling authentication, chunking, and conflict resolution automatically.

## Provider Abstraction Architecture

The synchronization system relies on a generic `SyncClient` interface defined in [`app/utils/cloud/index.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/utils/cloud/index.ts). This abstraction ensures that the high-level sync logic never directly interacts with provider-specific APIs.

```typescript
// app/utils/cloud/index.ts
export type SyncClient = {
  get: (key: string) => Promise<string>;
  set: (key: string, value: string) => Promise<void>;
  check: () => Promise<boolean>;
};

```

A factory function `createSyncClient` instantiates the appropriate concrete implementation based on the user's selected `ProviderType`:

```typescript
export function createSyncClient<T extends ProviderType>(
  provider: T,
  config: SyncClientConfig[T],
): SyncClient {
  return SyncClients[provider](config as any) as any;
}

```

This design allows [`app/store/sync.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/store/sync.ts) to treat WebDAV and Upstash identically, calling only `get()`, `set()`, and `check()` regardless of the backend storage mechanism.

## WebDAV Client Implementation

The WebDAV provider ([`app/utils/cloud/webdav.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/utils/cloud/webdav.ts)) stores the entire backup as a single JSON file using standard HTTP verbs.

**Authentication and Headers**

The client constructs a **Basic Auth** header by base64-encoding the username and password combination:

```typescript
const auth = btoa(config.username + ":" + config.password);
// Used in headers() method

```

**Storage Structure**

*   **File Location:** [`backup.json`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/backup.json) inside a folder defined by `STORAGE_KEY`
*   **Operations:** `MKCOL` (verify/create directory), `GET` (download), `PUT` (upload)
*   **Proxy Support:** When `store.useProxy` is enabled, requests route through `store.proxyUrl` to handle CORS restrictions

```typescript
const res = await fetch(this.path(fileName, proxyUrl), {
  method: "PUT",
  headers: this.headers(),
  body: value,
});

```

The `check()` method validates connectivity by issuing an `MKCOL` request to ensure the remote directory exists and is accessible.

## Upstash Client Implementation

The Upstash provider ([`app/utils/cloud/upstash.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/utils/cloud/upstash.ts)) interacts with the Upstash Redis HTTP API, automatically handling payload size limitations through intelligent chunking.

**Authentication**

Unlike WebDAV's Basic Auth, Upstash uses **Bearer token** authentication:

```typescript
return { Authorization: `Bearer ${config.apiKey}` };

```

**Chunking Strategy**

Because Upstash free tier limits request payloads to 1 MiB, the client splits large JSON states into chunks using the `chunks()` utility from `app/utils/format`:

```typescript
// Writing state in chunks
for await (const chunk of chunks(value)) {
  await this.redisSet(chunkIndexKey(index), chunk);
}
await this.redisSet(chunkCountKey, index.toString());

```

**Key Structure**

*   **Chunk Keys:** `<storeKey>-chunk-0`, `<storeKey>-chunk-1`, etc.
*   **Metadata Key:** `<storeKey>-chunk-count` stores the total number of chunks
*   **Reading:** The client fetches all chunk keys in sequence and concatenates them (`chunks.join("")`) to reconstruct the original JSON

## Sync Workflow and State Management

The central orchestration lives in [`app/store/sync.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/store/sync.ts) within the `useSyncStore` Zustand store.

**The Sync Process**

The `sync()` method executes a deterministic four-step workflow:

1.  **Capture Local State:** Retrieves current application state via `getLocalAppState()`
2.  **Fetch Remote:** Calls `client.get(config.username)` to retrieve the stored backup
3.  **Merge:** If remote data exists, merges it with local state using `mergeAppState()`; otherwise uses local state exclusively
4.  **Persist:** Writes the merged state back to the cloud via `client.set(config.username, JSON.stringify(localState))`
5.  **Timestamp:** Records completion time via `markSyncTime()`

**Client Instantiation**

The `getClient()` method dynamically creates the correct implementation:

```typescript
const client = this.getClient(); // Returns WebDAV or Upstash client based on store.provider

```

This provider-agnostic approach means the sync logic remains identical regardless of whether the user configured WebDAV or Upstash in their settings.

## Practical Implementation Examples

**Configure WebDAV Synchronization**

```typescript
import { useSyncStore, ProviderType } from "@/app/store/sync";

useSyncStore.setState({
  provider: ProviderType.WebDAV,
  useProxy: true,
  proxyUrl: "/api/cors/",
  webdav: {
    endpoint: "https://mydav.example.com/remote.php/webdav",
    username: "myuser",
    password: "mypassword",
  },
});

```

**Configure Upstash Synchronization**

```typescript
useSyncStore.setState({
  provider: ProviderType.UpStash,
  upstash: {
    endpoint: "https://mydb.upstash.io",
    apiKey: "AXXX...",
  },
});

```

**Trigger Manual Synchronization**

```typescript
async function performSync() {
  const sync = useSyncStore.getState();
  try {
    await sync.sync();
    console.log("Synchronization completed successfully");
  } catch (error) {
    console.error("Synchronization failed:", error);
  }
}

```

**Verify Provider Connectivity**

```typescript
const isReachable = await useSyncStore.getState().check();
// Returns true if the provider responds correctly, false otherwise

```

**Automated Periodic Sync**

```typescript
// Sync every 10 minutes
setInterval(() => {
  useSyncStore.getState().sync().catch(console.error);
}, 10 * 60 * 1000);

```

## Summary

*   NextChat uses a unified `SyncClient` interface in [`app/utils/cloud/index.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/utils/cloud/index.ts) to abstract provider-specific implementations.
*   **WebDAV** ([`app/utils/cloud/webdav.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/utils/cloud/webdav.ts)) stores data as a single [`backup.json`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/backup.json) file using Basic Auth and standard HTTP verbs, with optional CORS proxy support.
*   **Upstash** ([`app/utils/cloud/upstash.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/utils/cloud/upstash.ts)) stores data as multiple Redis keys using Bearer token authentication, automatically chunking payloads into 1 MiB segments to respect API limits.
*   The sync workflow in [`app/store/sync.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/store/sync.ts) merges remote and local states bidirectionally, ensuring chat history remains consistent across devices.
*   Both providers expose identical `get`, `set`, and `check` methods, making the high-level synchronization logic completely provider-agnostic.

## Frequently Asked Questions

### What is the maximum backup size supported by Upstash in NextChat?

Upstash free tier limits individual HTTP request payloads to 1 MiB. To accommodate this, NextChat automatically splits large state objects into chunks under this limit, storing them as separate Redis keys (`<storeKey>-chunk-<index>`) and reassembling them during retrieval. This effectively allows backups of any practical size supported by your Upstash database limits.

### How does NextChat authenticate with WebDAV servers?

NextChat uses **HTTP Basic Authentication** for WebDAV connections. The client base64-encodes the username and password combination (`btoa(username + ":" + password)`) and includes this in the Authorization header of every MKCOL, GET, and PUT request. The credentials are retrieved from the `SyncStore` configuration object where users save their WebDAV settings.

### Why does WebDAV use a single file while Upstash uses multiple keys?

WebDAV natively supports file-based storage, making it efficient to store the entire application state as one JSON file ([`backup.json`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/backup.json)) that gets overwritten atomically on each sync. Upstash is a key-value store with request size limitations, so NextChat implements a chunking strategy that splits the JSON into manageable segments. This allows the app to respect Upstash's 1 MiB request limit while still supporting full state backups that may exceed this size when uncompressed.

### What happens if a synchronization is interrupted halfway through?

NextChat's sync logic in [`app/store/sync.ts`](https://github.com/ChatGPTNextWeb/NextChat/blob/main/app/store/sync.ts) performs atomic operations at the provider level. For WebDAV, a PUT request either succeeds or fails as a single operation. For Upstash, if writing chunks fails partway through, the next successful sync will overwrite the previous partial state, as the `chunk-count` key only updates after all chunks are written successfully. However, the merge logic always happens locally before uploading, so local data remains safe even if the network upload fails.