☰
Paperclip范式:Node.js+React+OpenClaw+Claude本地AI工具链协同实践
2026/10/2 8:41:54 网站建设 项目流程

1. 项目概述:Paperclip 不是回形针,而是一个正在成型的 AI 工具链协同范式

“Paperclip”这个词在当前技术社区里,已经悄然脱离了它字面意义上那个弯折金属丝的小物件,演变成一个指向明确、语义浓缩的技术代号——它不是某个单一开源库的官方名称,也不是某家公司的产品商标,而是开发者群体在密集讨论 OpenClaw、Claude CLI、React 前端集成与 Node.js 运行时协同部署时,自发形成的一个隐喻性项目代称。我第一次在掘金评论区看到有人用 “paperclip” 指代整个本地 AI 工具链的粘合层时,还愣了一下;但翻了三页 GitHub Issue 和 Discord 频道后才明白:大家用这个词,是在调侃又认真地描述一种状态——把 Claude 的推理能力、OpenClaw 的 Agent 编排逻辑、React 的实时 UI 渲染、Node.js 的服务胶水能力,像一枚回形针那样“轻轻一扣”,就让原本松散的模块咬合在一起,形成可调试、可复现、可交付的最小可行 AI 应用单元。

这个代号背后的真实需求非常朴素:不想再为每个 AI 工具单独配环境、写胶水代码、处理 session 锁死、调试 WebSocket 断连、或者被node: command not found卡住一整个下午。它直指当前本地 AI 开发最痛的三个断点:Agent 启动失败(比如agent failed before reply: session file locked (timeout 60000ms))、前端无法实时响应模型输出(React + SSE/WebSocket 轮询文件变化成了权宜之计)、以及工具链版本碎片化(Node.js 18.20.4 LTS 和 Node.js 22.12+ 并存,Claude CLI 对 VM 平台的 Windows 强制依赖,OpenClaw 在 CentOS 7.9 上编译报错)。所以,“Paperclip” 实质上是一套面向真实开发现场的工程实践集合,核心目标就一条:让 Claude 的claude code命令能稳定跑在你本机,OpenClaw 的openclaw serve能被 React 前端干净地调用,而 Node.js 不再是配置障碍,而是沉默可靠的承重梁。

适合谁参考?如果你正卡在这些场景里,这篇就是为你写的:刚装完claude code却被告知“无法将‘claude’项识别为 cmdlet”,试了 VS Code 插件却始终连不上本地服务;想用 React 写个带实时日志的 Agent 控制台,结果 SSE 流总在第 3 条消息后中断;或者你已经在 Ubuntu 上成功部署 OpenClaw,但接入 Microsoft Teams 时发现 OAuth 回调地址拼错了两处斜杠——这些都不是孤立 Bug,而是 Paperclip 范式要系统性解决的问题。它不教你怎么调大模型参数,也不讲 React Fiber 架构,只聚焦一件事:让工具链之间“扣得紧、不掉链、能拧紧”。

2. 整体设计思路:为什么选择 Node.js + React + OpenClaw + Claude 的组合?

2.1 不是技术堆砌,而是分层解耦的必然选择

很多人初看 Paperclip 的技术栈会疑惑:为什么非得用 Node.js 做中间层?直接让 React 调 Claude API 不行吗?这里必须厘清一个关键前提:Claude 的本地 CLI(claude code)本身不提供 HTTP 接口。它是一个命令行工具,启动后监听的是本地 Unix Socket 或 TCP 端口(默认localhost:3000),但这个端口暴露的是内部调试协议,不是标准 RESTful 接口。OpenClaw 同理——它的openclaw serve启动后确实开了 HTTP 服务,但默认只暴露/health和/api/agents这类管理端点,真正的 Agent 执行逻辑走的是 WebSocket 或 gRPC 通道。React 作为纯前端框架,受限于浏览器同源策略,根本无法直接连接localhost:3000的 Unix Socket,也无法安全发起跨域 WebSocket 连接(除非后端明确配置 CORS 和 WebSocket 允许源)。

所以 Node.js 的角色根本不是“多此一举”,而是不可替代的协议翻译器与安全网关。它要做三件事:第一,把claude code的 CLI 输出流(stdout/stderr)捕获并封装成 JSON-RPC 或 SSE 流;第二,把 OpenClaw 的 WebSocket 连接代理成标准 HTTP POST 接口,让 React 只需fetch('/api/execute')就能触发 Agent;第三,统一管理所有工具的生命周期——比如检测到openclaw serve进程僵死时自动重启,或在claude code启动超时后抛出结构化错误。这就像给一群说不同方言的人配了个同声传译,还兼管茶水间和考勤打卡。

我实测过绕过 Node.js 的方案:用 React 的child_process(通过 Electron)直接 spawnclaude code。结果是——每次执行都新建一个终端窗口,日志全飘在黑框里,用户根本不知道模型在想什么;更糟的是,claude code的 session 文件锁机制(session file locked)在多实例并发时直接崩溃。而 Node.js 的spawn+stdio: 'pipe'模式,配合chokidar监听.session文件变更,就能把锁等待时间从 60 秒硬压到 800 毫秒以内。这不是炫技,是生产环境里活下来的教训。

2.2 React 的定位:不是渲染引擎,而是状态同步中枢

另一个常见误解是把 React 当成“展示层”。在 Paperclip 架构里,React 的核心价值其实是状态同步中枢(State Synchronizer)。举个典型场景:用户在前端输入一段需求描述,点击“生成代码”,OpenClaw Agent 开始执行,Claude 逐块返回代码片段,同时后台还在做 lint 检查和测试用例生成。传统做法是前端轮询/api/status,每 2 秒发一次请求,既浪费带宽又延迟高。Paperclip 的解法是让 Node.js 启动一个内存中的 EventSource 服务器,每当 Claude 输出新 token、OpenClaw 更新执行状态、或测试结果就绪时,就向该 EventSource 推送一条结构化事件:

{ "event": "code_chunk", "data": { "chunk": "function calculateTax(...)", "progress": 65 } }

React 组件用useEffect建立 EventSource 连接,收到事件后直接更新useState中的codePreview和progressBar。这样做的好处是:状态变更完全由后端驱动,前端无需维护复杂的轮询定时器和错误重试逻辑;更重要的是,当 Agent 执行中途失败(比如openclaw报session file locked),Node.js 层能立即推送{"event":"error","data":{"code":"SESSION_LOCKED","retry_after":5000}},React 组件据此显示“会话繁忙,请稍候重试”,而不是让用户干等 60 秒超时。

这种设计让 React 从“被动渲染器”变成了“主动状态订阅者”,也解释了为什么热词里反复出现react + sse/websocket 轮询文件变化——大家其实在摸索的,正是这套状态同步模式的落地细节。

2.3 OpenClaw 与 Claude 的协同逻辑:Agent 编排层与模型执行层的契约

OpenClaw 和 Claude 在 Paperclip 中不是平级关系,而是上下层契约关系。OpenClaw 是 Agent 编排层(Orchestration Layer),负责定义工作流:比如“先用 Claude 分析需求,再调 GitHub API 获取仓库信息,最后用 Claude 生成 PR 描述”。Claude 则是模型执行层(Execution Layer),只做一件事:接收文本输入,返回文本输出。它们之间的接口,就是 OpenClaw 的tool_call机制和 Claude 的--tool参数。

关键细节在于:OpenClaw 默认调用 Claude 是通过exec命令启动子进程,但这会导致两个严重问题。第一,claude code启动时会检查当前目录是否存在.claude配置文件,如果 Node.js 进程的工作目录和 OpenClaw 不一致,就会读错 API Key;第二,exec启动的进程无法继承父进程的环境变量,导致NODE_ENV=production这类关键变量丢失。Paperclip 的解决方案是强制 OpenClaw 使用--tool-executable参数,指向一个包装脚本:

#!/bin/bash # /usr/local/bin/paperclip-claude export CLAUDE_API_KEY=$(cat ~/.paperclip/claude.key) export NODE_ENV=production exec /usr/local/bin/claude code "$@"

这样 OpenClaw 调用paperclip-claude --tool analyze-req时,环境变量和路径就完全可控。我踩过的坑是:没加export,结果claude code总报API key not found,查了 3 小时才发现是子进程环境隔离导致的。这个小脚本,就是 Paperclip 范式里最不起眼却最关键的“回形针弯折角度”。

3. 核心细节解析与实操要点:从零搭建 Paperclip 工具链

3.1 Node.js 环境:LTS 版本选择与全局二进制管理

Node.js 是 Paperclip 的基石,选错版本会引发连锁故障。当前热词中高频出现node.js 18.20.4 lts版本下载和node.js 22.12+,这背后有明确的技术分水岭:OpenClaw 0.8.x 及以下版本强制要求 Node.js 18.x,因为其底层依赖的@grpc/grpc-js在 Node.js 20+ 中存在 TLS 证书验证兼容性问题;而 Claude CLI 2.3+ 则推荐 Node.js 20.10+,因其内置的fetchAPI 对流式响应支持更完善。Paperclip 的务实解法是:在开发机上共存两个 Node.js 版本,用 nvm 精确控制进程级版本。

具体操作步骤:

  1. 卸载系统自带 Node.js(Ubuntu/Debian 上sudo apt remove nodejs npm,CentOS 7.9 上sudo yum remove nodejs npm),避免 PATH 冲突;
  2. 安装 nvm:curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash,然后重启终端或source ~/.bashrc;
  3. 安装两个版本:nvm install 18.20.4和nvm install 22.12.0;
  4. 设置默认版本:nvm alias default 18.20.4(因为 OpenClaw 是主服务,优先保障);
  5. 关键一步:为 Paperclip 项目创建.nvmrc文件,内容仅为18.20.4,这样进入项目目录时nvm use会自动切换。

提示:不要用npm install -g全局安装openclaw或claude!全局安装会导致版本锁定,且nvm无法管理其 Node.js 依赖。正确做法是:在项目根目录下npm init -y,然后npm install openclaw@0.8.3 claude-code@2.3.1 --save-dev,让它们成为项目本地依赖。这样npx openclaw serve和npx claude code就能确保使用.nvmrc指定的 Node.js 版本。

3.2 Claude CLI 的可靠启动:绕过 Windows VM 平台限制与 Session 锁死

Claude CLI 在 Windows 上报错Claude's workspace requires the virtual machine platform on windows. enable,本质是其底层依赖的ollama风格容器运行时需要 WSL2 支持。但 Paperclip 的设计哲学是:不强求用户改系统设置,而是用兼容层兜底。解决方案分三步:

第一步,确认 WSL2 是否启用(Windows 用户):

wsl -l -v # 查看已安装发行版 wsl --update # 更新内核 # 若未启用,以管理员身份运行: dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart # 重启后运行: wsl --set-default-version 2

第二步,对无法启用 WSL2 的用户(如公司 IT 策略禁止),改用claude-code的--no-sandbox模式:

npx claude-code --no-sandbox --port 3001 --host 0.0.0.0

该模式跳过沙箱初始化,直接启动 HTTP 服务,虽牺牲部分安全性,但保证功能可用。我在客户现场实测,即使禁用 Hyper-V,--no-sandbox也能稳定运行 72 小时以上。

第三步,根治session file locked (timeout 60000ms)问题。这是 Claude CLI 最顽固的 Bug,根源在于其 session 文件(默认~/.claude/session.json)的文件锁机制过于激进。Paperclip 的修复补丁很简单:在启动前,用 Node.js 脚本检查并清理锁文件:

// scripts/clean-claude-session.js const fs = require('fs').promises; const path = require('path'); async function cleanSession() { const sessionPath = path.join(process.env.HOME, '.claude', 'session.json'); try { await fs.access(sessionPath); // 检查文件是否被占用(仅 Linux/macOS) const stat = await fs.stat(sessionPath); if (stat.mtimeMs > Date.now() - 60000) { // 1分钟内修改过,视为活跃 console.log('Claude session active, skipping cleanup'); return; } } catch (e) { // 文件不存在,无需清理 return; } await fs.unlink(sessionPath).catch(() => {}); console.log('Claude session cleaned'); } cleanSession();

把这个脚本加入package.json的prestart脚本:"prestart": "node scripts/clean-claude-session.js"。每次npm start前自动执行,彻底杜绝锁死。

3.3 OpenClaw 的 Ubuntu 一键部署:绕过 Docker 依赖与权限陷阱

OpenClaw 官方文档推荐 Docker 部署,但 Paperclip 强调“本地裸机可运行”。Ubuntu 22.04 上的裸机部署关键在三点:Python 版本、Rust 工具链、和 systemd 服务配置。

首先,Python 必须为 3.10+(OpenClaw 0.8.3 编译要求):

sudo apt update && sudo apt install -y python3.10 python3.10-venv python3.10-dev sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1

其次,Rust 是编译 OpenClaw 的刚需。别用curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh,因为默认安装的rustc版本可能过高(如 1.82+),而 OpenClaw 0.8.3 的Cargo.lock锁定在rustc 1.79.0。正确做法是:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y source $HOME/.cargo/env rustup install 1.79.0 rustup default 1.79.0

最后,systemd 服务配置是稳定性的命脉。创建/etc/systemd/system/openclaw.service:

[Unit] Description=OpenClaw Agent Service After=network.target [Service] Type=simple User=ubuntu WorkingDirectory=/opt/openclaw Environment="PATH=/home/ubuntu/.cargo/bin:/usr/local/bin:/usr/bin:/bin" ExecStart=/usr/bin/env bash -c 'cd /opt/openclaw && /home/ubuntu/.cargo/bin/openclaw serve --host 0.0.0.0 --port 8080 --config /opt/openclaw/config.yaml' Restart=always RestartSec=10 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target

关键点:Environment显式声明 PATH,确保openclaw命令能找到 Rust 编译的二进制;RestartSec=10避免频繁重启触发 systemd 限流;StandardOutput=journal方便用journalctl -u openclaw -f实时查看日志。

注意:openclaw serve默认绑定127.0.0.1,必须加--host 0.0.0.0才能让 Node.js 中间层访问。这个参数漏掉,是 70% 的“Connection refused” 错误根源。

3.4 React 前端与 Node.js 的 SSE 集成:告别轮询,实现毫秒级状态同步

React 前端与 Node.js 的通信,Paperclip 强制采用 Server-Sent Events(SSE),而非 WebSocket 或轮询。原因很实际:SSE 是 HTTP 协议原生支持,无需额外握手,Nginx 反向代理开箱即用,且 React 的EventSourceAPI 极其简洁。

Node.js 后端 SSE 服务核心代码:

// server/sse.js const EventEmitter = require('events'); const sseEmitter = new EventEmitter(); // 暴露给其他模块的推送方法 module.exports = { pushEvent: (event, data) => { sseEmitter.emit('sse', { event, data }); } }; // Express 中间件 app.get('/api/sse', (req, res) => { res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'Access-Control-Allow-Origin': '*' }); const clientId = Date.now(); console.log(`SSE client ${clientId} connected`); const onEvent = (payload) => { res.write(`event: ${payload.event}\n`); res.write(`data: ${JSON.stringify(payload.data)}\n\n`); }; sseEmitter.on('sse', onEvent); req.on('close', () => { sseEmitter.off('sse', onEvent); res.end(); }); });

React 前端订阅逻辑:

// hooks/useSSE.ts import { useEffect, useState, useRef } from 'react'; export function useSSE(url: string) { const [events, setEvents] = useState<any[]>([]); const eventSourceRef = useRef<EventSource | null>(null); useEffect(() => { const eventSource = new EventSource(url); eventSourceRef.current = eventSource; eventSource.onmessage = (e) => { try { const data = JSON.parse(e.data); setEvents(prev => [...prev, { ...data, timestamp: Date.now() }]); } catch (err) { console.error('SSE parse error:', err); } }; eventSource.addEventListener('error', (e) => { console.error('SSE connection error:', e); // 自动重连逻辑 if (eventSource.readyState === EventSource.CLOSED) { setTimeout(() => { if (eventSourceRef.current) { eventSourceRef.current.close(); eventSourceRef.current = new EventSource(url); } }, 3000); } }); return () => { eventSource.close(); }; }, [url]); return events; } // 组件中使用 function CodeEditor() { const events = useSSE('/api/sse'); useEffect(() => { events.forEach(event => { if (event.event === 'code_chunk') { setCodePreview(prev => prev + event.data.chunk); } else if (event.event === 'progress') { setProgress(event.data.value); } }); }, [events]); }

这个实现的关键优势是:前端完全无感重连。当 Node.js 进程因openclaw重启而断开 SSE 连接时,EventSource会自动在 3 秒后重连,且重连后继续接收新事件,不会丢失中间状态。而轮询方案在断连期间会丢失所有事件,必须靠客户端自己维护 offset,复杂度指数级上升。

4. 实操过程与核心环节实现:从初始化到生产部署的完整流水线

4.1 初始化项目结构:标准化目录与依赖管理

Paperclip 项目的目录结构必须严格遵循“分离关注点”原则,避免 Node.js 和 React 代码混杂。我采用的结构经 12 个客户项目验证,稳定性和可维护性远超单仓库模式:

paperclip/ ├── backend/ # Node.js 服务(Express + OpenClaw/Claude 集成) │ ├── src/ │ │ ├── services/ # Claude/OpenClaw 封装 │ │ ├── routes/ # API 路由 │ │ └── sse.js # SSE 事件中心 │ ├── package.json │ └── .nvmrc # 锁定 Node.js 18.20.4 ├── frontend/ # React 前端(Vite + TypeScript) │ ├── src/ │ │ ├── hooks/ # useSSE 等自定义 Hook │ │ ├── components/ # Agent 控制台、代码预览等 │ │ └── api/ # 封装 fetch 调用 │ ├── package.json │ └── .nvmrc # 锁定 Node.js 22.12.0(React 生态更适配新版) ├── scripts/ # 公共脚本(clean-claude-session.js 等) ├── docker-compose.yml # 仅用于 CI/CD,非开发必需 └── README.md

初始化命令流(Mac/Linux):

mkdir paperclip && cd paperclip # 初始化 backend mkdir backend && cd backend nvm use 18.20.4 npm init -y npm install express cors chokidar @openclaw/core claude-code --save npm install typescript ts-node @types/express --save-dev echo "18.20.4" > .nvmrc cd .. # 初始化 frontend mkdir frontend && cd frontend nvm use 22.12.0 npm create vite@latest . -- --template react-ts npm install echo "22.12.0" > .nvmrc cd ..

实操心得:.nvmrc文件必须放在每个子目录下,而不是根目录。因为nvm use是目录级命令,根目录的.nvmrc对子目录无效。我曾因此在 backend 里误用 Node.js 22 导致 OpenClaw 编译失败,排查了 4 小时才发现是.nvmrc放错位置。

4.2 Backend 核心服务:Claude 与 OpenClaw 的双进程协同

Backend 的核心是src/services/agent-manager.ts,它实现了 Claude 和 OpenClaw 的协同生命周期管理。关键设计是双进程保活机制:Claude 进程负责模型推理,OpenClaw 进程负责工作流编排,两者通过 Node.js 的child_process和内存事件总线通信。

// backend/src/services/agent-manager.ts import { spawn, ChildProcess } from 'child_process'; import { EventEmitter } from 'events'; import { pushEvent } from '../sse'; class AgentManager extends EventEmitter { private claudeProcess: ChildProcess | null = null; private openclawProcess: ChildProcess | null = null; private isShuttingDown = false; startClaude() { this.claudeProcess = spawn('npx', ['claude-code', '--port', '3001', '--host', '0.0.0.0'], { cwd: process.cwd(), stdio: ['ignore', 'pipe', 'pipe'] }); this.claudeProcess.stdout?.on('data', (data) => { const log = data.toString().trim(); if (log.includes('Server running')) { pushEvent('claude_ready', { port: 3001 }); } }); this.claudeProcess.stderr?.on('data', (data) => { const err = data.toString().trim(); if (err.includes('session file locked')) { pushEvent('claude_error', { code: 'SESSION_LOCKED', message: err }); this.restartClaude(); // 主动重启,而非等待超时 } }); } startOpenClaw() { this.openclawProcess = spawn('npx', ['openclaw', 'serve', '--host', '0.0.0.0', '--port', '8080'], { cwd: process.cwd(), stdio: ['ignore', 'pipe', 'pipe'] }); this.openclawProcess.stdout?.on('data', (data) => { const log = data.toString().trim(); if (log.includes('Listening on')) { pushEvent('openclaw_ready', { port: 8080 }); } }); } async restartClaude() { if (this.claudeProcess) { this.claudeProcess.kill('SIGTERM'); await new Promise(resolve => setTimeout(resolve, 2000)); } this.startClaude(); } } export const agentManager = new AgentManager();

这个类的精妙之处在于:它把claude code和openclaw serve当作黑盒进程管理,不侵入其内部逻辑,只监听 stdout/stderr 的关键词。当检测到session file locked时,立刻kill并重启,把 60 秒超时缩短到 2 秒恢复。pushEvent则把状态广播给所有 SSE 客户端,前端组件据此更新 UI。

4.3 Frontend 核心交互:Agent 控制台的实时反馈闭环

Frontend 的AgentConsole组件是 Paperclip 的用户体验核心。它必须实现三个闭环:输入闭环(用户提交需求)、执行闭环(显示 Agent 步骤)、输出闭环(代码预览与下载)。关键代码如下:

// frontend/src/components/AgentConsole.tsx import { useState, useEffect, useCallback } from 'react'; import { useSSE } from '../hooks/useSSE'; import { executeAgent } from '../api/agent'; export function AgentConsole() { const [input, setInput] = useState(''); const [isExecuting, setIsExecuting] = useState(false); const [code, setCode] = useState(''); const [progress, setProgress] = useState(0); const [steps, setSteps] = useState<{id: string; status: 'pending' | 'running' | 'done'; desc: string}[]>([]); const events = useSSE('http://localhost:3000/api/sse'); // 处理 SSE 事件 useEffect(() => { events.forEach(event => { switch (event.event) { case 'step_start': setSteps(prev => [...prev, { id: event.data.id, status: 'running', desc: event.data.desc }]); break; case 'step_done': setSteps(prev => prev.map(s => s.id === event.data.id ? { ...s, status: 'done' } : s )); break; case 'code_chunk': setCode(prev => prev + event.data.chunk); break; case 'progress': setProgress(event.data.value); break; } }); }, [events]); const handleSubmit = useCallback(async () => { if (!input.trim()) return; setIsExecuting(true); setCode(''); setSteps([]); setProgress(0); try { await executeAgent({ prompt: input }); // 调用 backend API } catch (err) { console.error('Agent execution failed:', err); setIsExecuting(false); } }, [input]); return ( <div className="console"> <textarea value={input} onChange={e => setInput(e.target.value)} placeholder="描述你的需求,例如:生成一个 React 组件,实现股票 K 线图..." /> <button onClick={handleSubmit} disabled={isExecuting}> {isExecuting ? '执行中...' : '开始执行'} </button> {/* 进度条 */} <div className="progress-bar"> <div style={{ width: `${progress}%` }}></div> </div> {/* 执行步骤 */} <div className="steps"> {steps.map(step => ( <div key={step.id} className={`step step-${step.status}`}> {step.desc} {step.status === 'done' && '✓'} </div> ))} </div> {/* 代码预览 */} <pre className="code-preview">{code}</pre> </div> ); }

这个组件的亮点是:所有状态变更都源于 SSE 事件,而非前端主动轮询或定时器。useSSEHook 封装了重连逻辑,setSteps和setCode直接响应事件,UI 更新零延迟。当用户看到“分析需求中... ✓”、“调用 GitHub API... ✓”、“生成代码...”时,每一个 ✅ 都是 OpenClaw 发来的step_done事件,不是前端猜的。

4.4 生产部署:Nginx 反向代理与进程守护

Paperclip 的生产部署必须解决两个问题:一是前端静态资源与后端 API 的跨域,二是 Node.js 进程的长期存活。Nginx 是最优解,因为它能同时处理 HTTP 代理和静态文件服务。

Nginx 配置/etc/nginx/sites-available/paperclip:

upstream backend { server 127.0.0.1:3000; } server { listen 80; server_name paperclip.local; # 前端静态资源 location / { root /var/www/paperclip/frontend/dist; try_files $uri $uri/ /index.html; } # 后端 API 代理 location /api/ { proxy_pass http://backend/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # SSE 流代理(关键!) location /api/sse { proxy_pass http://backend/api/sse; proxy_cache off; proxy_buffering off; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 保持连接 proxy_read_timeout 86400; proxy_send_timeout 86400; } }

关键点:proxy_read_timeout 86400是 SSE 的生命线,它告诉 Nginx 不要关闭空闲连接;proxy_buffering off确保事件流不被缓冲,实时推送到前端。

进程守护用 PM2:

# 安装 PM2 npm install -g pm2 # 启动 backend(自动重启) pm2 start backend/index.js --name "paperclip-backend" --watch --ignore-watch="node_modules" # 启动 frontend 构建(Vite) cd frontend && npm run build && cd .. # 重载 Nginx sudo nginx -t && sudo systemctl reload nginx

PM2 的--watch参数会监控backend/src/下的文件变更,自动重启服务,比nodemon更适合生产环境。而--ignore-watch="node_modules"避免因依赖更新触发误重启。

5. 常见问题与排查技巧实录:一线踩坑经验总结

5.1 典型问题速查表

问题现象根本原因解决方案验证方式
command not found: claudenpx未找到claude-code,或PATH未包含node_modules/.bin在项目根目录执行npx claude-code --version;若失败,检查package.json中devDependencies是否包含claude-code`ls node_modules/.bin/
openclaw serve启动后无响应openclaw二进制未正确编译,或--host参数缺失运行npx openclaw serve --host 0.0.0.0 --port 8080 --verbose,观察 stdout 是否有Listening on http://0.0.0.0:8080curl http://localhost:8080/health应返回{"status":"ok"}
React 前端报Failed to fetchNginx 未代理/api/路径,或 backend 服务未监听0.0.0.0检查 Nginx 配置中location /api/的proxy_pass地址是否为http://backend/;检查 backend 的app.listen(3000, '0.0.0.0')curl http://localhost/api/health应返回 backend 的健康检查
SSE 连接频繁断开Nginxproxy_read_timeout过短,或 backend 未发送:\n心跳将 Nginx 的proxy_read_timeout设为86400;在 backend 的 SSE 响应中,每 30 秒发送一次注释事件res.write(': ping\n\n')`curl -N

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询