ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Next.js 接入 PlanetScale MySQL 一次跑通实战

Next.js 接入 PlanetScale MySQL 一次跑通实战 Next.js 接入 PlanetScale MySQL 一次跑通实战【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.jsNext.js 官方仓库在 examples/with-mysql 里放了一个现成的全栈模板App Router Prisma ORM PlanetScale MySQL Tailwind CSS 的电商商品列表。本文以它为蓝本把建库、发放凭据、推送 Schema、灌入种子数据、本地运行到生产部署的整条链路走一遍并拆解开其中的关键机制——Prisma Driver Adapter 如何绕过 MySQL 协议、新版prisma-client生成器输出到哪里、globalForPrisma单例为什么挂在globalThis上。读完你可以得到一套能直接搬进自己项目的 Next.js 接入 PlanetScale MySQL 方案。️ 为什么是这套组合先回答一个问题为什么选 App Router Prisma 托管 MySQL而不是自建数据库加手写 SQL三者各填一块空白——Next.js 的 Server Component 允许在服务端直接查库渲染页面省掉一层 APIPrisma 用类型安全的方式描述查询模型和类型都由 Schema 推导PlanetScale 是基于 Vitess 的托管 MySQL 兼容服务不需要运维且自带分支能力。Tailwind CSS v4 只负责样式与数据链路无关。这个示例很小但文件职责划分是完整的后面每一层机制都能在对应文件里找到落点文件职责prisma/schema.prisma数据模型Product/Category与关系定义prisma/data.ts演示数据3 个分类、4 件商品prisma/seed.ts种子脚本清空、重置自增、批量写入prisma.config.tsPrisma CLI 配置读DATABASE_URL、声明 seed 命令lib/prisma.ts应用运行时使用的 PrismaClient 单例封装app/page.tsxasync Server Component服务端取数components/Product.tsx商品卡片图片、名称、价格、分类标签从空目录到商品网格一条主线这一节把所有命令按执行顺序排开跟敲即可。建库只需一条命令但要先理解分支前置条件装好 Node.js 和 PlanetScale CLIpscale。首次使用先完成认证命令会引导你在浏览器里走 OAuthpscale auth login然后创建数据库pscale database create DATABASE_NAME --engine mysql建库成功后PlanetScale 会自动生成一个名为main的分支后续步骤的BRANCH_NAME直接用它。分支是 PlanetScale 的核心概念每个库都以main为起点开发分支可以独立读写 Schema 和数据再通过部署请求合并回主分支。本教程全程在main上操作分支模型在生产环节才体现价值见后文上线前先把分支提升。拉取模板一条 create-next-appnpx create-next-app --example with-mysql nextjs-mysql--example参数让 create-next-app 直接把仓库里的 examples/with-mysql 作为模板拉下来并安装依赖。用其他包管理器时对应写法包管理器命令npmnpx create-next-app --example with-mysql nextjs-mysqlyarnyarn create next-app --example with-mysql nextjs-mysqlpnpmpnpm create next-app --example with-mysql nextjs-mysqlbunbunx create-next-app --example with-mysql nextjs-mysql模板的依赖集很精简值得知道谁在干什么运行时是prisma/client数据访问、prisma/adapter-planetscalePlanetScale 驱动适配器和undici为适配器提供 fetch 实现开发依赖里prisma是 CLItsx负责跑 TypeScript 种子脚本dotenv负责加载.env另有tailwindcssv4 及tailwindcss/postcss。凭据即连接串四个字段拼一个 DATABASE_URL先把环境变量模板复制出来mv .env.example .env再为目标分支创建一组凭据pscale password create DATABASE_NAME BRANCH_NAME PASSWORD_NAMEPASSWORD_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用返回的字段拼出.env里DATABASE_URL的值mysql://USERNAME:PLAIN_TEXT_PASSWORDACCESS_HOST_URL/DATABASE_NAME?sslacceptstrict占位符来源说明USERNAMECLI 返回的USERNAME数据库用户名PLAIN_TEXT_PASSWORDCLI 返回的PLAIN TEXT明文密码仅展示一次ACCESS_HOST_URLCLI 返回的ACCESS HOST URL数据库访问域名DATABASE_NAME建库时指定的名字目标库名?sslacceptstrict手动追加强制要求 TLS 加密否则拒绝连接这条连接串有两处消费者别漏配Prisma CLI 侧由 prisma.config.ts 中的env(DATABASE_URL)读取应用运行侧由 lib/prisma.ts 的process.env.DATABASE_URL读取。正因如此prisma.config.ts与 prisma/seed.ts 顶部都显式写了import dotenv/config——否则 Prisma 命令和种子脚本读不到项目根目录的.env。generate、db push、db seed三条命令的先后顺序npx prisma generate # 按 schema 生成类型安全的客户端 npx prisma db push # 把数据模型直接同步到数据库 npx prisma db seed # 运行 tsx prisma/seed.ts 写入演示数据顺序不能乱generate产出客户端代码db push建表seed才有表可写。三条命令各自的角色prisma generate按generator client的output路径本模板是lib/generated/prisma生成客户端类型从此可从/lib/generated/prisma/client导入prisma db push不生成迁移文件而是比对 Schema 与现有库结构后直接变更适合原型阶段prisma db seed实际执行tsx prisma/seed.ts——这条命令写在 package.json 的prisma.seed字段里prisma.config.ts 中migrations.seed声明了相同路径两处保持一致。启动后数据是怎么流动的npm run dev打开 http://localhost:3000 就能看到商品网格。这条链路里没有 API 层app/page.tsx 本身就是一个async Server Component带async的服务端组件可在渲染前 await 数据库查询const products await prisma.product.findMany({ include: { category: true }, });一次查询联表取出商品及其所属分类结果逐个传给 components/Product.tsxnext/image展示商品图渲染名称、描述、价格与分类标签整体样式由 Tailwind 完成页面元数据标题 PlanetScale MySQL Next.js定义在 app/layout.tsx。客户端拿到的只是渲染后的 HTML数据库访问完全发生在服务端——这就是 App Router 时代推荐的取数姿势。 拆开代码看三个关键实现Schema 里四个值得停下的细节prisma/schema.prisma 全文很短但每处配置都有原因generator client { provider prisma-client output ../lib/generated/prisma } datasource db { provider mysql relationMode prisma }一对多关系Product通过categoryId外键列引用Category并在该列上加了index([categoryId])加速按分类查询Category一侧用products Product[]声明反向关系。price用Decimal而非浮点货币字段避免精度误差种子数据里对应地用new Prisma.Decimal(19.95)构造见 prisma/data.ts。provider prisma-client是新版 TypeScript 生成器客户端代码输出到项目内的lib/generated/prisma而不是传统的node_modules/.prisma。所以应用与种子脚本都从/lib/generated/prisma/client这类本地路径导入类型——生成物在项目里类型检查天然可用。datasource里不写url连接串统一由 prisma.config.ts 提供db push、generate等命令执行时由 CLI 读取该配置文件Schema 文件因此保持干净。relationMode prisma数据库层不建外键约束PlanetScale 的默认场景关系完整性改由 Prisma 在应用层维护。连接为什么走 HTTPS 而不是 MySQL 协议传统 Prisma 客户端直接与数据库建立 MySQL 协议的 TCP 连接但这个模板换成了Driver Adapter模式prisma/adapter-planetscale接收连接串后把所有查询通过 HTTPS 发往 PlanetScale 的 Serverless Driver 网关由网关代为执行客户端本身不再直连数据库。看 lib/prisma.ts 的构造部分const adapter new PrismaPlanetScale({ url: process.env.DATABASE_URL, fetch: undiciFetch, }); return new PrismaClient({ adapter });这里能解释依赖表里undici的存在走 HTTPS 意味着需要一个 fetch 实现来发请求适配器通过构造参数接收它模板传入的是 Node 环境下的undicifetch。prisma/seed.ts 里的适配器构造与之一模一样——种子脚本不在 Next.js 运行时里必须自己显式建实例。为什么把 PrismaClient 挂到 globalThis 上lib/prisma.ts 的尾部是经典封装const prisma globalForPrisma.prisma ?? createPrismaClient(); if (process.env.NODE_ENV ! production) globalForPrisma.prisma prisma;问题出在开发模式文件变更会触发模块反复重新执行若每次执行都new PrismaClient()就会不断创建新连接最终撞上连接数上限。解法是把实例挂到globalThis上——??保证只在首次创建热更新后复用旧实例而挂载动作只发生在NODE_ENV ! production时因为生产环境模块只加载一次本就不需要全局缓存。种子脚本先清空再写入的幂等策略prisma/seed.ts 的执行顺序是deleteMany()清空Product与Category两张表 → 两条$executeRaw把两张表的AUTO_INCREMENT重置为 1 →createMany批量写入 3 个分类和 4 件商品。重置自增是刻意的商品通过categoryId: 1/2/3硬引用分类主键只要保证先插分类、再插商品且自增从 1 开始外键引用就永远有效。清空在前则保证整个脚本可重复执行结果恒定——这就是幂等重跑db seed不会累积脏数据。 上线前提升分支与凭据隔离本地跑通后上线前要先处理数据库的分支角色。PlanetScale 中main默认是开发角色必须显式提升才能作为生产流量入口pscale branch promote DATABASE_NAME BRANCH_NAME凭据有两种选择继续复用本地那组或推荐用pscale password create为生产单独发一组。隔离的收益是本地与生产互不干扰且可以单独轮换其中任何一组不用动另一边。接下来部署到 Vercel导入nextjs-mysql仓库在环境变量中配置DATABASE_URL填入针对生产分支构造好的连接串完成部署即可。示例页面全部是服务端渲染构建期不需要连库应用会在每次请求时通过服务端查询 PlanetScale。两条红线生产环境的DATABASE_URL不要沿用仅限本地使用的分支凭据连接串保留?sslacceptstrict确保传输加密不被降级。按症状排错PrismaClientInitializationError/ 连接失败逐字段核对.env里DATABASE_URL的四个占位符是否都换成了 CLI 实际返回值注意明文密码只出现过一次且域名末尾没有多余路径。页面能打开但表或数据为空确认依次执行过npx prisma db push建表和npx prisma db seed灌数据种子脚本先清空再写入重跑安全。种子脚本报读不到DATABASE_URLseed.ts与prisma.config.ts都靠dotenv/config加载根目录的.env检查执行命令时的当前目录是否位于项目根目录。开发期报连接数超限检查业务代码是否绕开了 lib/prisma.ts 的单例封装而直接new PrismaClient()开发模式下必须复用挂在globalThis上的实例。把这套骨架搬进自己的项目替换 prisma/schema.prisma 中的模型与 prisma/data.ts 的种子数据为业务定义其余链路不动建库、发凭据、拼连接串的流程原样复用只换库名保留 lib/prisma.ts 的单例封装业务代码统一经它取数上线前提升分支并单独发放生产凭据本地与生产互不干扰。【免费下载链接】next.jsThe React Framework项目地址: https://gitcode.com/GitHub_Trending/next/next.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进