如何在不同服务商部署基于tRPC的React与Express应用?
问题描述
我有一个React应用和一个Express应用,两者均已配置使用tRPC。客户端的utils/trpc.ts通过本地相对路径导入AppRouter类型:
import { createTRPCReact } from '@trpc/react-query'; import type { AppRouter } from '../../../chatapp-server/src/api/root'; export const trpc = createTRPCReact<AppRouter>();
已成功将Express应用部署在Render上,但部署React应用到Vercel时失败,报错包含两个核心问题:
- 客户端通过相对路径查找
AppRouter类型,但Vercel构建环境中没有服务器代码,触发TS2307模块找不到错误。 - 后端router中存在与tRPC内置方法冲突的命名(如
useContext、Provider、createClient),导致类型推断错误,出现TS2339属性不存在的报错。
完整报错信息:
src/App.tsx(4,25): error TS2339: Property 'message' does not exist on type '"The property 'useContext' in your router collides with a built-in method, rename this router or procedure on your backend." | "The property 'Provider' in your router collides with a built-in method, rename this router or procedure on your backend." | "The property 'createClient' in your router collides with a built...'. Property 'message' does not exist on type '"The property 'useContext' in your router collides with a built-in method, rename this router or procedure on your backend."'. src/main.tsx(14,8): error TS2339: Property 'createClient' does not exist on type '"The property 'useContext' in your router collides with a built-in method, rename this router or procedure on your backend." | "The property 'Provider' in your router collides with a built-in method, rename this router or procedure on your backend." | "The property 'createClient' in your router collides with a built...'. Property 'createClient' does not exist on type '"The property 'useContext' in your router collides with a built-in method, rename this router or procedure on your backend."'. src/main.tsx(29,11): error TS2339: Property 'Provider' does not exist on type '"The property 'useContext' in your router collides with a built-in method, rename this router or procedure on your backend." | "The property 'Provider' in your router collides with a built-in method, rename this router or procedure on your backend." | "The property 'createClient' in your router collides with a built...'. Property 'Provider' does not exist on type '"The property 'useContext' in your router collides with a built-in method, rename this router or procedure on your backend."'. src/main.tsx(33,12): error TS2339: Property 'Provider' does not exist on type '"The property 'useContext' in your router collides with a built-in method, rename this router or procedure on your backend." | "The property 'Provider' in your router collides with a built-in method, rename this router or procedure on your backend." | "The property 'createClient' in your router collides with a built...'. Property 'Provider' does not exist on type '"The property 'useContext' in your router collides with a built-in method, rename this router or procedure on your backend."'. src/utils/trpc.ts(2,32): error TS2307: Cannot find module '../../../chatapp-server/src/api/root' or its corresponding type declarations. ELIFECYCLE Command failed with exit code 2. Error: Command "pnpm run build" exited with 1 BUILD_UTILS_SPAWN_1: Command "pnpm run build" exited with 1
涉及的客户端代码:App.tsx:
import { trpc } from "./utils/trpc" function App() { const { data } = trpc.message.getAll.useQuery(); console.log(data); return ( <> <h1> asd </h1> </> ) } export default App
main.tsx:
import React from 'react' import ReactDOM from 'react-dom/client' import App from './App' import './index.css' import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { httpBatchLink } from '@trpc/client'; import { trpc } from './utils/trpc'; import superjson from 'superjson'; const queryClient = new QueryClient(); const trpcClient = trpc.createClient({ transformer: superjson, links: [ httpBatchLink({ url: import.meta.env.VITE_SERVER_URL || '', async headers() { return {} }, }), ], }) ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( <React.StrictMode> <trpc.Provider client={trpcClient} queryClient={queryClient}> <QueryClientProvider client={queryClient}> <App /> </QueryClientProvider> </trpc.Provider> </React.StrictMode>, )
可行性说明
完全可行,tRPC原生支持前后端分离部署,核心是解决类型共享和命名冲突两个问题。
解决办法
1. 修复命名冲突问题
报错明确指出,后端router中存在与tRPC客户端内置方法重名的路由/过程(useContext、Provider、createClient),这些名称是tRPC客户端的保留属性,不能用于后端定义。
操作步骤:
- 打开后端Express应用的路由定义文件(如
src/api/root.ts)。 - 查找所有名为
useContext、Provider、createClient的路由或过程,将其重命名为非保留名称:- 示例:
useContext改为getUserContext,Provider改为AppServiceProvider,createClient改为createAppClient。
- 示例:
- 重启后端服务,确认路由正常运行。
2. 解决类型共享问题
本地开发时用相对路径导入类型可行,但Vercel构建环境无服务器代码,必须更换类型共享方式,以下是三种常用方案:
方案一:使用Monorepo结构(推荐)
将React客户端和Express服务器放在同一仓库,通过Monorepo工具(如pnpm workspaces、TurboRepo)共享类型目录,确保Vercel构建时能访问到类型文件。
操作步骤:
- 调整仓库结构:
your-project/ ├── packages/ │ ├── client/ # React应用 │ ├── server/ # Express应用 │ └── trpc-types/ # 共享类型目录
- 在
server/src/api/root.ts中导出AppRouter,并将类型定义同步到trpc-types目录(或直接在trpc-types中定义路由类型)。 - 在客户端
package.json中添加共享类型依赖:
{ "dependencies": { "trpc-types": "workspace:*" } }
- 修改客户端
utils/trpc.ts的导入路径:
import { createTRPCReact } from '@trpc/react-query'; import type { AppRouter } from 'trpc-types'; export const trpc = createTRPCReact<AppRouter>();
- 配置Vercel构建:在项目设置中将Root Directory设为
packages/client,确保Monorepo依赖解析正常。
方案二:将类型发布为私有npm包
把tRPC的AppRouter类型单独抽取为npm包,发布到私有仓库(如npm私有包、GitHub Packages),再在客户端和服务器中安装该包。
操作步骤:
- 创建新npm包项目,导出
AppRouter类型:
// trpc-types/src/index.ts import type { AppRouter } from '../path/to/server/src/api/root'; export type { AppRouter };
- 将包发布到私有npm仓库。
- 在客户端和服务器的
package.json中安装该包:
pnpm install trpc-types
- 修改客户端
utils/trpc.ts的导入路径为npm包:
import { createTRPCReact } from '@trpc/react-query'; import type { AppRouter } from 'trpc-types'; export const trpc = createTRPCReact<AppRouter>();
方案三:构建时复制类型文件到客户端
在客户端构建脚本中,先从服务器复制类型文件到客户端目录,再执行构建。
操作步骤:
- 在客户端
package.json中添加预处理脚本:
{ "scripts": { "prebuild": "cp ../chatapp-server/src/api/root.ts src/types/trpc-root.ts", "build": "vite build" } }
(若服务器在远程,可在后端添加接口返回类型文件内容,用curl命令拉取)
2. 修改客户端utils/trpc.ts的导入路径:
import { createTRPCReact } from '@trpc/react-query'; import type { AppRouter } from './types/trpc-root'; export const trpc = createTRPCReact<AppRouter>();
验证部署
完成修改后:
- 重新部署Express服务器到Render,确保路由命名修改生效。
- 部署React应用到Vercel,检查构建是否成功。
- 访问Vercel上的React应用,验证tRPC请求是否正常发送到Render服务器。
内容的提问来源于stack exchange,提问作者Timotei Oros
相关产品推荐
相关产品推荐

