前端转全栈 04:Next.js 服务端——Server/Client Components 与 Route Handlers

前端全栈tutorialnextjsserver-componentsroute-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 })
}

要点:

  1. request: Request 是标准 Web 请求对象(和浏览器 fetch 的 Request 同构)。
  2. NextResponse.json(data, { status }) 返回 JSON 响应——对应第 2 篇讲的状态码。
  3. 服务端从 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_role key 能绕过一切权限检查,只能存在于服务端环境变量,一旦泄露等于把数据库拱手让人。后面安全篇会再强调。


四、什么时候用 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.