TypeScript开发的Next.js应用部署Vercel时构建失败求助
Vercel构建失败:Prisma UserCreateInput类型不匹配错误
环境信息
依赖版本(取自package.json)
{ "@prisma/client": "^4.11.0", "@supabase/supabase-js": "^2.12.1", "next": "^12.2.5", "next-auth": "^4.20.1", "react": "17.0.2", "typescript": "4.5.5" }
技术栈
- 框架:Next.js
- 数据库:Supabase
- 图片存储:Supabase Storage
- ORM:Prisma
- 表单:react-hook-form
- 开发语言:TypeScript
问题现象
本地执行npm run dev、npm run build、npm run start均正常,用户注册、更新及图片获取功能无异常。但仅在Vercel构建时出现类型错误,导致部署失败。数据库中存储图片的字段类型为string,推测是Next.js与Prisma的类型定义不匹配,但无法定位具体原因。
Vercel构建错误日志
./pages/api/user/index.tsx:19:9 Type error: Type '{ name: any; email: any; password: any; image: any; }' is not assignable to type '(Without<UserCreateInput, UserUncheckedCreateInput> & UserUncheckedCreateInput) | (Without<...> & UserCreateInput)'. Object literal may only specify known properties, and 'image' does not exist in type '(Without<UserCreateInput, UserUncheckedCreateInput> & UserUncheckedCreateInput) | (Without<...> & UserCreateInput)'.
相关代码
用户注册API(pages/api/user/index.tsx)
import prisma from "../../../lib/prisma"; import type { NextApiRequest, NextApiResponse } from "next"; import { hash } from "bcrypt"; export default async function handler( req: NextApiRequest, res: NextApiResponse ) { if (req.method === "POST") { const { name, email, password, image } = req.body; console.log(req.body); const hash_password = await hash(password, 10); const data = await prisma.user.create({ data: { name: name, email: email, password: hash_password, image: image, }, }); return res.status(200).json(data); } else if (req.method === "GET") { const data = await prisma.user.findMany({}); return res.status(200).json(data); } }
用户注册组件(pages/user/index.tsx)
// pages/user/index.tsx import { useForm } from "react-hook-form"; import { useRouter } from "next/router"; import Link from "next/link"; import { signIn } from "next-auth/react"; import Image, { StaticImageData } from "next/image"; import { useState, useRef } from "react"; import { supabase } from "../../lib/supabase"; // 用户默认图片 const defaultImg = process.env.NEXT_PUBLIC_DEFAULT_IMG; interface UserInput { name: string; email: string; password: string; image?: string | StaticImageData; } type uploadImageUrl = string; export default function UserForm() { const [uploadImageUrl, setUploadImageUrl] = useState<uploadImageUrl>(); const [uploadImageFile, setUploadImageFile] = useState<File>(); const router = useRouter(); const { register, handleSubmit, formState: { errors }, } = useForm<UserInput>({ defaultValues: { name: "", email: "", password: "", image: defaultImg, }, }); // 选择图片后显示预览 const onChangeFile = (e: React.ChangeEvent<HTMLInputElement>) => { const { files } = e.target; setUploadImageUrl(URL.createObjectURL(files[0])); setUploadImageFile(files[0]); }; const submitUserRegister = async (input: UserInput) => { // 将图片上传至Supabase const { data, error } = await supabase.storage .from("photos") .upload("user/" + uploadImageFile.name, uploadImageFile); // 从Supabase获取图片URL const url = await supabase.storage .from("photos") .getPublicUrl(JSON.stringify(data)); const { publicUrl } = url.data; // 将获取到的URL存入image字段 const formData = { name: input.name, email: input.email, password: input.password, image: publicUrl, }; try { const res = await fetch("/api/user", { method: "POST", body: JSON.stringify(formData), headers: { "Content-Type": "application/json", }, }); // 以JSON格式返回 const data = await res.json(); const email = data.email; // 使用表单输入的密码,避免使用已哈希的密码 const password = formData.password; // 直接通过signIn登录 await signIn("credentials", { email, password, callbackUrl: "/", redirect: true, }); } catch (error) { console.error("Error registering user:", error); } }; return ( <div> <h3>用户注册</h3> <form onSubmit={handleSubmit(submitUserRegister)} className="w-full max-w-sm" > --- 非必要部分已省略 --- <Image src={uploadImageUrl ? uploadImageUrl : defaultImg} width={100} height={100} ></Image> <label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white"> Upload file </label> <input className="block w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 dark:text-gray-400 focus:outline-none dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400" id="file_input" type="file" onChange={onChangeFile} /> <div className="md:flex md:items-center"> <div className="md:w-2/3"> <button className="shadow bg-blue-500 hover:bg-blue-400 focus:shadow-outline focus:outline-none text-white font-bold py-2 px-4 rounded"> <Link href={`/`}>Home</Link> </button> </div> <div className="md:w-2/3"> <button className="shadow bg-blue-500 hover:bg-blue-400 focus:shadow-outline focus:outline-none text-white font-bold py-2 px-4 rounded" type="submit" > Sign Up </button> </div> </div> </form> </div> ); }
Prisma Schema(schema.prisma)
model User { id String @default(cuid()) @id name String? email String? @unique emailVerified DateTime? image String? password String createdAt DateTime @default(now()) @map(name: "created_at") updatedAt DateTime @updatedAt @map(name: "updated_at") posts Post[] @@map(name: "users") }
内容的提问来源于stack exchange,提问作者user21514407
相关产品推荐
相关产品推荐

