Next.js 用 Server Components 直连数据库:去掉 API 层的边界,和三条别踩的安全红线

Next.js 用 Server Components 直连数据库:去掉 API 层的边界,和三条别踩的安全红线

写惯了 React + REST 的人第一次看 Next.js App Router 会懵:组件里直接 await db.query(...)?那我的 /api/users 路由呢?说好的前后端分离呢?其实 Server Components 允许你在组件里直接查数据库,省掉一整层 API,但这不是「随便写」------用错了会把数据库连接、密钥甚至整张表都泄露到浏览器。这篇讲清楚边界在哪,以及三条不能碰的红线。

传统写法:多一层 API 的样板代码

以前拿一个用户列表,你得写两头。后端一个路由:

ts 复制代码
// app/api/users/route.ts
export async function GET() {
  const users = await db.user.findMany();
  return Response.json(users);
}

前端一个组件,还要处理 loading、error、竞态:

tsx 复制代码
'use client';
function UserList() {
  const [users, setUsers] = useState([]);
  useEffect(() => {
    fetch('/api/users').then(r => r.json()).then(setUsers);
  }, []);
  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

数据本来就在服务器上,却要序列化成 JSON、走一趟 HTTP、再在浏览器反序列化。API 路由纯粹是个中转,没有任何业务价值。

Server Components 写法:组件即查询

App Router 里,默认组件就是 Server Component,可以是 async 函数,直接在里面查库:

tsx 复制代码
// app/users/page.tsx ------ 默认就是 Server Component,不用加任何标记
import { db } from '@/lib/db';

export default async function UsersPage() {
  // 直接查库,这段代码只在服务器跑,永远不会进浏览器 bundle
  const users = await db.user.findMany();
  return (
    <ul>
      {users.map(u => <li key={u.id}>{u.name}</li>)}
    </ul>
  );
}

没有 /api/users,没有 useState,没有 useEffect,没有 loading 状态样板。组件渲染时数据已经在了。这段 db.user.findMany() 的代码、你的数据库连接字符串、ORM 全都留在服务器,发给浏览器的只有渲染好的 HTML。

边界在哪:Server 和 Client 的分水岭

核心心智模型:默认全是 Server Component,加了 'use client' 才变 Client Component

  • Server Component :能 await、能直连数据库、能读环境变量密钥,但不能用 useState/useEffect/onClick 等交互
  • Client Component :能交互、能用 hooks,但不能直接查库,拿数据得靠 props 或 Server Actions。

典型的组合是:Server Component 查数据,把结果作为 props 传给 Client Component 做交互:

tsx 复制代码
// page.tsx (Server) ------ 查数据
export default async function Page() {
  const users = await db.user.findMany();
  return <UserTable users={users} />; // 数据当 props 传下去
}

// UserTable.tsx (Client) ------ 负责交互
'use client';
export function UserTable({ users }) {
  const [sort, setSort] = useState('name');
  // ...排序、点击等交互逻辑
}

红线一:props 会被序列化发到浏览器

Server Component 传给 Client Component 的 props 会被序列化并发到浏览器。如果你把整条数据库记录塞过去,里面的敏感字段(password hash、内部 token)就全泄露了------即使 UI 上没显示,打开 DevTools 的网络面板照样能看到:

tsx 复制代码
// 危险!整个 user 对象(含 passwordHash)被序列化发到浏览器
return <Profile user={user} />;

// 正确:只挑要用的字段传下去
return <Profile user={{ id: user.id, name: user.name, avatar: user.avatar }} />;

记住:传给 Client 的 props,等于公开数据。查库可以查全字段,传下去前必须裁剪。

红线二:密钥别在会被 Client 引入的模块里

db 客户端、密钥这些只能在服务端用的东西,一旦被某个 Client Component import,就会被打进浏览器 bundle。为防止手滑,用 server-only 包做硬隔离:

ts 复制代码
// lib/db.ts
import 'server-only'; // 一旦被 Client Component 引入,构建直接报错

export const db = createClient(process.env.DATABASE_URL!);

加了这行,任何 'use client' 文件误引 lib/db 都会在构建期炸掉,而不是悄悄把连接串泄露到线上。这是比「小心点」可靠得多的护栏。

红线三:别在渲染里做「写」操作

Server Component 是渲染逻辑,可能因预取、并发渲染被执行多次,只读 是安全的。任何写操作(增删改)必须放到 Server Action 或 Route Handler,由用户动作显式触发:

tsx 复制代码
// 危险!渲染时删数据,页面刷新/预取都可能触发
export default async function Page({ searchParams }) {
  if (searchParams.del) await db.user.delete({ where: { id: searchParams.del } });
  // ...
}

// 正确:写操作放进 Server Action,由表单提交触发
async function deleteUser(id: string) {
  'use server';
  await db.user.delete({ where: { id } });
  revalidatePath('/users');
}

渲染只读、变更走 Action------把这条刻进肌肉记忆,能躲开一大类「刷新页面数据莫名其妙被改」的诡异 bug。

小结

  • Server Component 可以 async 直连数据库,省掉纯中转的 API 层和一堆 loading 样板。
  • 默认全是 Server Component,'use client' 才切到 Client;交互归 Client,取数归 Server。
  • 红线一:传给 Client 的 props 等于公开数据,传前裁剪字段。
  • 红线二 :用 import 'server-only' 给数据库/密钥模块上硬隔离,防止被打进浏览器。
  • 红线三:渲染只读,写操作一律走 Server Action。

一句话记忆点:Server Components 不是「前端能连数据库了」,而是「查询代码留在服务器、只把渲染结果和裁剪过的数据发给浏览器」------边界感没了,泄露就来了。

相关推荐
学习zhao极致it1 小时前
2020全新React教程全家桶实战redux+antd+React Hooks前端js视频
前端
用户921080262861 小时前
Bubble 消息操作区改造:复制、重新生成和反馈
前端
LEE1 小时前
AI Agent 都在疯狂加功能,它说:我全砍了
前端·后端
张清悠1 小时前
用 TRAE Work 把 PRD + 设计稿注释,整理成 20 分钟可排期前端任务清单
前端
迷迭香yy1 小时前
行业板块轮动因子实战从板块资金到因子建模的本地化Python全流程
数据库·人工智能·python
不知疲倦的老鸟2 小时前
Next.js 15 多语言站点的 6 个坑:从 query string 路由到 URL 路径
typescript·next.js
SelectDB2 小时前
抖音集团 实时数据仓库:Apache Doris / SelectDB 的技术能力与实践
数据库
SelectDB2 小时前
网易 日志与时序数据分析:Apache Doris / SelectDB 的技术能力与实践
数据库
Brown.alexis2 小时前
es6知识点5-自备使用
前端·javascript·es6