使用Next.js+tRPC实现注册接口遇resolver is not a function错误
问题排查与T3栈注册功能最优实现
一、先解决「TypeError: resolver is not a function」500错误
这个报错的核心是你把tRPC路由和普通Next.js API路由的逻辑搞混了,常见坑点:
- 错误地把tRPC Router直接导出为Next.js API路由,没用到tRPC的
createNextApiHandler包装 - signUpRouter里的procedure resolver定义格式不对(比如写成了对象而非函数)
- 把tRPC子路由合并到appRouter的方式出错
快速修复步骤
情况1:你想用普通Next.js API做注册(不通过tRPC)
直接把pages/api/auth/signup.ts改成标准API格式,别掺和tRPC:
// pages/api/auth/signup.ts import type { NextApiRequest, NextApiResponse } from "next"; import { prisma } from "../../../server/db/client"; import bcrypt from "bcrypt"; export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method !== "POST") return res.status(405).json({ msg: "只接受POST请求" }); const { email, password, name } = req.body; if (!email || !password || !name) return res.status(400).json({ msg: "必填字段不能为空" }); try { // 检查用户是否已存在 const existingUser = await prisma.user.findUnique({ where: { email } }); if (existingUser) return res.status(409).json({ msg: "该邮箱已注册" }); // 加密密码 const hashedPwd = await bcrypt.hash(password, 10); const user = await prisma.user.create({ data: { email, name, hashedPassword: hashedPwd } }); return res.status(201).json({ userId: user.id }); } catch (err) { console.error(err); return res.status(500).json({ msg: "服务器内部错误" }); } }
情况2:你想用tRPC处理注册(推荐,符合T3栈规范)
T3栈的tRPC路由都要通过主入口pages/api/trpc/[trpc].ts处理,单独的signup.ts不该是tRPC路由文件:
- 定义tRPC注册路由:
// server/api/routers/auth.ts import { z } from "zod"; import { router, publicProcedure } from "../trpc"; import { prisma } from "../../db/client"; import bcrypt from "bcrypt"; export const authRouter = router({ signUp: publicProcedure .input(z.object({ email: z.string().email(), password: z.string().min(6), name: z.string().min(1) })) .mutation(async ({ input }) => { const { email, password, name } = input; const existingUser = await prisma.user.findUnique({ where: { email } }); if (existingUser) throw new Error("该邮箱已注册"); const hashedPwd = await bcrypt.hash(password, 10); const user = await prisma.user.create({ data: { email, name, hashedPassword: hashedPwd } }); return { userId: user.id, email: user.email }; }) });
- 合并到根路由:
// server/api/root.ts import { router } from "./trpc"; import { authRouter } from "./routers/auth"; export const appRouter = router({ auth: authRouter }); export type AppRouter = typeof appRouter;
- 前端用tRPC客户端调用(别用fetch):
// 前端组件示例 import { api } from "../utils/api"; const SignUpForm = () => { const signUpMutate = api.auth.signUp.useMutation({ onSuccess: () => window.location.href = "/", onError: (err) => alert(err.message) }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); signUpMutate.mutate({ email: "test@example.com", password: "123456", name: "张三" }); }; return ( <form onSubmit={handleSubmit}> {/* 表单输入框省略 */} <button type="submit" disabled={signUpMutate.isLoading}> {signUpMutate.isLoading ? "注册中..." : "注册"} </button> </form> ); };
二、T3栈注册功能最优实践(结合NextAuth自动登录)
既然用了NextAuth,注册完成后直接自动登录才是流畅体验,完整实现如下:
1. 后端tRPC路由(集成NextAuth登录)
// server/api/routers/auth.ts import { z } from "zod"; import { router, publicProcedure } from "../trpc"; import { prisma } from "../../db/client"; import bcrypt from "bcrypt"; import { signIn } from "next-auth/react"; export const authRouter = router({ signUp: publicProcedure .input(z.object({ email: z.string().email(), password: z.string().min(6), name: z.string().min(1) })) .mutation(async ({ input }) => { const { email, password, name } = input; const existingUser = await prisma.user.findUnique({ where: { email } }); if (existingUser) throw new Error("该邮箱已注册"); const hashedPwd = await bcrypt.hash(password, 10); await prisma.user.create({ data: { email, name, hashedPassword: hashedPwd } }); // 注册成功后自动登录 await signIn("credentials", { email, password, redirect: false // 禁止自动跳转,前端自行处理 }); return { success: true }; }) });
2. 确保NextAuth的CredentialsProvider配置正确
// server/auth.ts import type { NextAuthOptions } from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; import { prisma } from "./db/client"; import bcrypt from "bcrypt"; export const authOptions: NextAuthOptions = { providers: [ CredentialsProvider({ name: "账号密码登录", credentials: { email: { label: "邮箱", type: "email" }, password: { label: "密码", type: "password" } }, async authorize(credentials) { if (!credentials?.email || !credentials?.password) return null; const user = await prisma.user.findUnique({ where: { email: credentials.email } }); if (!user || !user.hashedPassword) return null; const isValidPwd = await bcrypt.compare(credentials.password, user.hashedPassword); return isValidPwd ? user : null; } }) ], session: { strategy: "jwt" } };
三、避坑总结
- 别混合tRPC路由和普通API:tRPC路由必须走主入口
pages/api/trpc/[trpc].ts,单独API文件别用tRPC Router - 密码必须加密:用bcrypt,绝对不能明文存数据库
- 用tRPC的类型安全:用Zod做输入验证,避免前端传参错误
- 注册后自动登录:结合NextAuth的
signIn方法提升用户体验
内容的提问来源于stack exchange,提问作者RicardoFonseca
相关产品推荐
相关产品推荐

