You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js API无法转发同步URL至PouchDB,数据同步失败

Next.js + CouchDB 同步故障排查(Cookie认证模式)

CouchDB 配置文件(local.ini)

[admins]
admin = <REDACTED>

[couchdb]
database_dir = <REDACTED>
view_index_dir = <REDACTED>
uri_file = <REDACTED>
uuid = <REDACTED>

[cluster]
n = 1
q = 2

[couch_peruser]
enable = true
delete_dbs = true
database_prefix = <REDACTED>

[log]
writer = file
file = <REDACTED>

[chttpd_auth]
secret = <REDACTED>

[chttpd]
enable_cors = true
bind_address = 0.0.0.0
authentication_handlers = {chttpd_auth, cookie_authentication_handler}, {chttpd_auth, jwt_authentication_handler}, {chttpd_auth, default_authentication_handler}, {chttpd_auth, proxy_authentication_handler}

[jwt_keys]
hmac:_default = <REDACTED>
required_claims = exp, sub
roles_claim_path = _couchdb.roles

[cors]
credentials = true
methods = GET, PUT, POST, HEAD, DELETE
headers = accept, authorization, content-type, origin, referer
origins = *

[httpd]
bind_address = 0.0.0.0

问题描述

开发Next.js项目时,采用Cookie身份认证,通过自定义API路由与CouchDB后端同步数据,但当前无法正常获取数据和启动同步。

环境配置

Next.js API 路由(api/sync)

该路由负责转发请求到CouchDB完成数据同步:

import { NextRequest, NextResponse } from "next/server";
import { cookies } from "next/headers";

export const handler = async (request: NextRequest) => {
  const cookieStore = await cookies();
  const authtoken = cookieStore.get("access_token");
  const database = cookieStore.get("database_name");

  if (!authtoken || !database) {
    return NextResponse.json(
      { error: "Missing authentication token or database" },
      { status: 400 },
    );
  }

  try {
    const syncResponse = await fetch(
      `http://127.0.0.1:5984/${database.value}`,
      {
        method: "GET",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${authtoken.value}`,
          Referer: "http://localhost:3001",
        },
      },
    );

    if (!syncResponse.ok) {
      const errorDetails = await syncResponse.text();
      console.error(
        `Sync request failed. Status: ${syncResponse.status}, Details: ${errorDetails}`,
      );
      return NextResponse.json(
        {
          error: `Sync failed with status ${syncResponse.status}`,
          details: errorDetails,
        },
        { status: syncResponse.status },
      );
    }

    const syncData = await syncResponse.json();
    return NextResponse.json(syncData);
  } catch (error: any) {
    console.error("Error forwarding sync request:", error);
    return NextResponse.json({ error: error.message }, { status: 500 });
  }
};

export { handler as GET, handler as POST };

PouchDB 同步代码

通过调用上述API实现本地与后端的数据同步:

import PouchDB from "pouchdb";
import PouchDBFind from "pouchdb-find";
import { QueryClient } from "@tanstack/react-query";

// 匿名化常量(替换为通用名称)
import {
  docType1,
  docType2,
  docType3,
  docType4,
  docType5,
  docType6,
  docType7,
} from "./constants/global.constants";

PouchDB.plugin(PouchDBFind);

export const db = <T extends {}>(
  queryClient?: QueryClient,
): PouchDB.Database<T> => {
  let invalidateTimeout: NodeJS.Timeout | null = null;

  // 防抖失效函数
  const debouncedInvalidateQueries = (queryClient: QueryClient | undefined) => {
    if (invalidateTimeout) clearTimeout(invalidateTimeout);
    invalidateTimeout = setTimeout(() => {
      const keys = [
        docType1,
        docType2,
        docType3,
        docType4,
        docType5,
        docType6,
        docType7,
      ];
      keys.forEach((key) =>
        queryClient?.invalidateQueries({ queryKey: [key] }),
      );
    }, 500); // 可调整防抖时间
  };

  let wasPaused = false;

  // 使用通用API URL,避免暴露敏感服务器地址
  const url = "http://localhost:3001/api/sync/"; // 生产环境修改或使用环境变量
  const opts = { live: true, retry: true };

  // 使用通用数据库名称隐藏应用细节
  const localDb = new PouchDB<T>("generic-local-db", { skip_setup: true });

  try {
    localDb.replicate.from(url).on("complete", function (info) {
      // 双向、持续、可重试的同步
      localDb
        .sync(url, opts)
        .on("change", (info) => {
          console.log("Sync change:", info);
          debouncedInvalidateQueries(queryClient); // 防抖失效
        })
        .on("paused", (err) => {
          if (!wasPaused) {
            console.log("Sync paused:", err);
            if (err) {
              console.error("Sync paused due to error:", err);
            } else {
              console.log("Sync paused, possibly up-to-date.");
            }
            wasPaused = true; // 标记已暂停
          }
        })
        .on("active", () => {
          if (wasPaused) {
            console.log("Sync resumed.");
            wasPaused = false; // 标记已恢复
          }
        })
        .on("denied", (err) => {
          console.error("Sync denied due to permissions:", err);
        })
        .on("error", (err) => {
          console.error("Error during one-off sync:", err);
        });
    });

    return localDb;
  } catch (error) {
    console.error("Error during one-off sync:", error);
    throw error;
  }
};

错误日志

GET /dashboard/items 200 (409ms)
GET /api/sync 200 (368ms)
GET /api/sync 200 (169ms)
GET /api/sync 200 (175ms)
GET /api 404 (235ms)
GET /api 404 (196ms)
GET /api 404 (194ms)
GET /api/sync 200 (97ms)
GET /api/sync 200 (118ms)
GET /api/sync/_changes 404 (184ms)
GET /api/sync 200 (224ms)
GET /api/sync/_changes 404 (211ms)
GET /api/sync/_changes 404 (143ms)

临时可行方案

硬编码访问令牌时同步可正常工作,但这种方式会导致Cookie在浏览器中可被JavaScript访问,存在安全风险:

const remoteDb = new PouchDB<T>(remoteDbUrl, {
  skip_setup: true,
  fetch: function (url, opts) {
    const headers = new Headers(opts?.headers);
    headers.set("Content-Type", "application/json");
    headers.set("Authorization", `Bearer ${accessToken}`);

    return PouchDB.fetch(url, { ...opts, headers });
  },
});

内容的提问来源于stack exchange,提问作者Tony Ngomana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 13:26:07