前端转全栈 04:Next.js 服务端——Server/Client Components 与 Route Handlers
系列目录:本文是「前端转全栈:Next.js + Supabase 实战」系列的第 4 篇。前面讲了 HTTP 和数据库,这一篇把它们和你的老本行 Next.js 接起来——你会发现服务端代码就写在同一个项目里。
好消息:做全栈你不需要另学一门后端语言。Next.js App Router 让你用同一套 React/TS,把服务端逻辑直接写进项目。
一、Server Component vs Client Component
这是 App Router 最关键的概念,也是前端转全栈的第一个认知拐点。
| | Server Component(默认) | Client Component |
|---|---|---|
| 运行位置 | 服务端 | 浏览器 |
| 能访问数据库? | ✅ 能 | ❌ 不能 |
| 能用 useState? | ❌ 不能 | ✅ 能 |
| 文件标注 | 不用标 | 顶部加 "use client" |
默认都是 Server Component——也就是说,你的组件函数其实跑在服务器上,可以直接读数据库、读密钥,结果以 HTML 发给浏览器。
// app/blog/[slug]/page.tsx —— Server Component,可直接查库
import { createClient } from "@/lib/supabase/server"
export default async function Page({ params }: { params: { slug: string } }) {
const supabase = await createClient()
const { data: post } = await supabase
.from("posts").select("*").eq("slug", params.slug).single()
return <article>{post.title}</article> // 直接渲染,无需 API 中转
}
凡是需要交互(点击、输入、状态),才加 "use client" 把它变成客户端组件。原则:尽量用 Server Component,交互边界再用 Client Component。
二、Route Handlers:你写的 API
有时客户端需要主动调接口(比如提交评论、点赞),这时用 Route Handlers:在 app/api/xxx/route.ts 里导出 GET / POST 等函数。
看本站真实的 src/app/api/comments/route.ts:
// src/app/api/comments/route.ts
import { NextResponse } from "next/server"
import { createClient } from "@/lib/supabase/server"
export async function POST(request: Request) {
const supabase = await createClient()
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}
const body = await request.json()
const { post_slug, content } = body
if (!post_slug || !content) {
return NextResponse.json({ error: "post_slug and content are required" }, { status: 400 })
}
const { data, error } = await supabase.from("comments").insert({
post_slug, content,
author_name: user.user_metadata?.name || "Anonymous",
user_id: user.id,
}).select().single()
if (error) return NextResponse.json({ error: error.message }, { status: 500 })
return NextResponse.json(data, { status: 201 })
}
要点:
request: Request是标准 Web 请求对象(和浏览器fetch的 Request 同构)。NextResponse.json(data, { status })返回 JSON 响应——对应第 2 篇讲的状态码。- 服务端从 cookie 解析出
user(身份在请求头里),这正是 HTTP 那一篇说的「服务端读请求四要素」。
三、服务端三件套:Supabase 客户端
本站把 Supabase 客户端拆成三个,对应三种运行环境:
| 文件 | 用途 | 凭据 |
|------|------|------|
| src/lib/supabase/client.ts | 浏览器端 | anon key(公开) |
| src/lib/supabase/server.ts | Server Component / Route Handler | anon key + 用户 cookie |
| src/lib/supabase/admin.ts | 服务端特权操作 | service_role key(绝不可进浏览器) |
// server.ts 关键:把请求的 cookie 喂给 Supabase,还原当前用户
export async function createClient() {
const cookieStore = await cookies()
return createServerClient(url, anonKey, {
cookies: {
getAll() { return cookieStore.getAll() },
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options))
},
},
})
}
⚠️
service_rolekey 能绕过一切权限检查,只能存在于服务端环境变量,一旦泄露等于把数据库拱手让人。后面安全篇会再强调。
四、什么时候用 Server Component,什么时候用 Route Handler?
经验法则:
- 页面初始化数据(文章详情、列表)→ 直接在 Server Component 里查库,不发额外请求,性能最好。
- 客户端触发的写操作 / 实时交互(提交评论、点赞)→ 用 Route Handler 或 Server Action(下一篇)。
- 给第三方 / 移动端用的接口 → 必须 Route Handler(对外暴露的 HTTP API)。
总结
- Server Component 默认就能跑在服务端、直接读库;要交互才加
"use client"。 - Route Handlers(
app/api/*/route.ts)= 你写的 API,导出GET/POST等函数。 - 三个 Supabase 客户端对应三种环境,密钥权限各不相同。
- 你不需要新语言——Next.js 本身就是全栈框架。
下一篇,我们深入 Server Component 的「数据获取与缓存」,让你的全栈应用既正确又飞快。
练习:在你的 Next.js 项目里新建
app/api/hello/route.ts,导出GET返回{ message: "hello fullstack" },用curl localhost:3000/api/hello验证。你已经写出了第一个后端接口。
Comments
Sign in to leave a comment.