Next.js 接入 PlanetScale MySQL:用 with-mysql 示例把电商页跑通真实数据
【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js
想让 Next.js 接入 PlanetScale MySQL,往往卡在两处:连接串拼不对,开发时热更新又把 Prisma 客户端反复重建,连接数很快超限。with-mysql 示例就是干这件事的:一个 App Router + Prisma + PlanetScale + Tailwind CSS 的全栈启动模板,把建库、凭据、Schema、种子数据到部署的链路装进一个可运行的商品列表。下面按数据流向把这个骨架拆开讲。
一条数据看全貌:商品行如何从数据库流到浏览器
先别看依赖清单,顺着数据走更直观。
数据落在 PlanetScale——基于 Vitess 的托管 MySQL 服务。运行时真正搬查询的是@prisma/adapter-planetscale:这个 Driver Adapter(负责把 Prisma 查询转发到数据库的适配层)并不直说 MySQL 协议,而是把 SQL 走 HTTPS 发到 PlanetScale 的 Serverless Driver 网关,依赖里的undici就是为它提供 fetch 实现的。适配层之上是 Prisma 生成的类型安全客户端;应用层里 app/page.tsx 作为 async Server Component 在服务端直接prisma.product.findMany({ include: { category: true } }),一次联表取出商品和分类;最后 components/Product.tsx 把每行渲染成卡片,Tailwind CSS v4 负责外观。全程没有单独的 API 层,查询在服务端完成,浏览器只收到渲染好的 HTML。
各文件的分工拆开看:
| 路径 | 职责 | 为什么存在 |
|---|---|---|
| lib/prisma.ts | PrismaClient 单例,实例化 PlanetScale 适配器 | 热更新时复用连接,避免耗尽连接数 |
| prisma/schema.prisma | Product/Category 模型与生成器配置 | 决定表结构和客户端产出路径 |
| prisma/seed.ts | 种子脚本 | 幂等重建,可重复执行 |
| prisma/data.ts | 3 个分类、4 件商品的种子数据 | 页面演示数据 |
| prisma.config.ts | Prisma CLI 配置 | 给命令提供连接串、指向种子脚本 |
| .env.example | DATABASE_URL模板 | CLI 与运行时共读同一变量 |
| app/page.tsx | 服务端取数 | 数据流的起点 |
| components/Product.tsx | 商品卡片 | 渲染单行数据 |
| package.json | 依赖与prisma.seed | 定义种子命令的执行方式 |
动手前:先懂分支,再建库,再拉项目
PlanetScale 的基本单元是分支(branch)。建库时会自动生成一个main分支,开发分支让你在不碰生产分支的前提下随便改 schema 和数据,再通过 deploy request(把开发分支合并回主分支的合并请求)合回去。正因为有这个设计,后面"把分支提升为生产分支"这一步才顺理成章。
命令行入口统一是pscale。装好 PlanetScale CLI 后先完成认证:
pscale auth login浏览器里走完 OAuth,凭据存在本地供后续命令使用。然后建一个 MySQL 引擎的库:
pscale database create <DATABASE_NAME> --engine mysql建完即可把main直接当作下文命令里的BRANCH_NAME。
项目本身用官方模板拉取,不用手装依赖:
npx create-next-app --example with-mysql nextjs-mysql换包管理器对应改成:
yarn create next-app --example with-mysql nextjs-mysql pnpm create next-app --example with-mysql nextjs-mysql bunx create-next-app --example with-mysql nextjs-mysql命令会在nextjs-mysql目录里装齐 package.json 声明的全部依赖。依赖不多但各司其职:运行时是next/react/react-dom、@prisma/client、@prisma/adapter-planetscale、undici;开发侧是prisma、tsx(跑种子脚本)、dotenv、TypeScript 相关,以及 Tailwind v4 与@tailwindcss/postcss。
打通数据库连接:先生成凭据拼出连接串,再读 Schema
先把环境模板变成真实文件:
mv .env.example .env再用 CLI 给分支创建一组凭据。PASSWORD_NAME只是给这组凭据起的名字——同一分支可以挂多组凭据,靠它区分本地开发、生产,也方便单独轮换:
pscale password create <DATABASE_NAME> <BRANCH_NAME> <PASSWORD_NAME>返回的明文密码只展示这一次,立刻存好:
Password <PASSWORD_NAME> was successfully created in <DIRECTORY_NAME>. Please save the values below as they will not be shown again NAME USERNAME ACCESS HOST URL ROLE PLAIN TEXT ---------------- -------------- ----------------------------- ----------------- ----------------------------- <PASSWORD_NAME> xxxxxxxxxxxxx xxxxxx.us-east-2.psdb.cloud Can Read & Write pscale_pw_xxxxxxx把返回的四个字段拼成标准 MySQL 连接串,作为.env里DATABASE_URL的值:
mysql://<USERNAME>:<PLAIN_TEXT_PASSWORD>@<ACCESS_HOST_URL>/<DATABASE_NAME>?sslaccept=strict?sslaccept=strict固定追加,含义是强制 TLS 加密连接。这个变量有两处消费方:CLI 侧由 prisma.config.ts 经env("DATABASE_URL")读取;运行时侧由 lib/prisma.ts 从process.env.DATABASE_URL读取。两者都不在 Next.js 的请求上下文里,所以这两个文件顶部都显式import "dotenv/config",确保根目录的.env被加载。
Schema 里哪些字段决定了连接串怎么用
prisma/schema.prisma 全文不长:
generator client { provider = "prisma-client" output = "../lib/generated/prisma" } datasource db { provider = "mysql" relationMode = "prisma" } model Product { id Int @id @default(autoincrement()) name String description String price Decimal image String category Category @relation(fields: [categoryId], references: [id]) categoryId Int @@index([categoryId]) } model Category { id Int @id @default(autoincrement()) name String description String products Product[] }几个地方值得追问一句"为什么这么写":
- 为什么
provider = "prisma-client"还带output?这是 Prisma 新版 TypeScript 生成器的写法:客户端代码输出到lib/generated/prisma而不是传统的node_modules/.prisma,所以应用和种子脚本都从"@/lib/generated/prisma/client"这个本地路径导入类型与客户端。 - 为什么 datasource 块里没有
url?连接串统一由prisma.config.ts提供,prisma db push、prisma generate等 CLI 命令执行时读取该配置。 - 为什么
relationMode = "prisma"?PlanetScale 默认没有外键约束,关系完整性改由 Prisma 在应用层维护。Product通过categoryId引用Category,该列上补了@@index加速按分类查询。 - 为什么运行时要 adapter 而不是直连 TCP?因为真正建立连接的就是适配器:它接收连接串后把查询经 HTTPS 转发给 Serverless Driver 网关,客户端不再说 MySQL 协议,
undici的 fetch 因此被传给它。
price用Decimal是为了避开货币字段的浮点误差,种子数据同样用new Prisma.Decimal(...)构造精确值。另外提醒:本示例面向 PlanetScale 的 MySQL 兼容服务,如果你用的是 PlanetScale Postgres,驱动适配器和连接串格式都不同,不能混用。
让数据跑起来:generate、push、seed,然后本地运行
三条命令按顺序执行:
npx prisma generate这一步按generator client的output路径产出类型安全客户端,TS 从此认识@/lib/generated/prisma/client。
npx prisma db push不生成迁移文件,把当前 Schema 直接推进数据库建表,适合原型阶段。
npx prisma db seed实际执行tsx prisma/seed.ts(命令定义在 package.json 的prisma.seed字段,与 prisma.config.ts 的migrations.seed保持一致)。
种子脚本的策略是幂等重建——可以重复执行、结果不变:先deleteMany()清空两张表,再用$executeRaw把AUTO_INCREMENT归零到 1,最后createMany批量写入 data.ts 里的 3 个分类(Hats / Socks / Shirts)和 4 件商品。因果关系在这里:商品用categoryId: 1/2/3这种固定值引用分类主键,只有重置自增,先插入的分类才真正落在 1/2/3 上,外键引用才永远成立。
启动开发服务器:
npm run dev浏览器打开 http://localhost:3000,就能看到从数据库渲染出来的商品网格 😄
取数入口 app/page.tsx 是 async Server Component,服务端直接findMany;app/layout.tsx 声明了 "PlanetScale MySQL + Next.js" 的页面元数据。
这里藏着一个新手容易踩的点,在 lib/prisma.ts:
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined; }; function createPrismaClient() { const adapter = new PrismaPlanetScale({ url: process.env.DATABASE_URL, fetch: undiciFetch, }); return new PrismaClient({ adapter }); } const prisma = globalForPrisma.prisma ?? createPrismaClient(); if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; export default prisma;为什么绕这一圈?开发模式下模块会随文件变更反复重执行,每次重执行都new PrismaClient()的话,旧连接不释放又开新连接,很快触发连接数上限。所以把实例挂到globalThis上,??保证只创建一次,热更新后复用旧实例;生产环境模块只加载一次,于是仅在非生产环境才挂全局。
上生产:promote 分支,再配好 Vercel 环境变量
PlanetScale 里main默认是开发角色,只有被提升(promote)后才能承接生产流量:
pscale branch promote <DATABASE_NAME> <BRANCH_NAME>提升之后,凭据有两个选择:直接沿用本地开发用的那组,或再pscale password create一组新的,让开发、生产凭据独立、可单独轮换。
部署本身只有三步:
- 在 Vercel 导入
nextjs-mysql仓库; - 环境变量里添加
DATABASE_URL,值填前文格式拼好的生产连接串; - 完成。页面全部服务端渲染,构建期不碰数据库,每次请求由服务端直查 PlanetScale。
两个提醒:生产上别沿用仅限本地的分支凭据;连接串保留?sslaccept=strict。
排障按"现象 → 原因 → 处理"各一条:
PrismaClientInitializationError/ 连接失败→ 连接串四个占位符有没替换的,或域名带了多余路径 → 对照 CLI 返回逐项核对。- 表建了但没数据→
db push和db seed有漏跑 → 按顺序补跑,种子幂等不怕重复。 - 种子脚本读不到环境变量→ 命令没在项目根目录执行,
dotenv/config没加载到.env→ 回到根目录重跑。 - 开发期连接数超限→ 有代码绕过 lib/prisma.ts 单例直接
new PrismaClient()→ 统一走单例导出。
这套骨架替换掉数据模型与种子数据,就能原样搬进真实业务:改 schema 里的两个 model、换掉 data.ts 的数组,就得到你自己的 Next.js + PlanetScale + Prisma 版本。
【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考