前端转全栈 05:服务端数据获取与缓存——既正确又飞快
系列目录:本文是「前端转全栈:Next.js + Supabase 实战」系列的第 5 篇。上一篇我们能在 Server Component 里读库了,这一篇解决「读到的数据何时更新、如何不每次都打数据库」。
前端习惯在浏览器里 useEffect + fetch 拉数据。到了全栈,数据获取挪到了服务端,缓存模型也完全不同——这是最容易踩坑的地方。
一、两种渲染:静态 vs 动态
Next.js 会判断一个页面「能不能提前生成好」:
- 静态(Static):页面内容不随请求变化(如关于页)。构建时生成一次,之后直接发缓存,最快。
- 动态(Dynamic):依赖请求参数或实时数据(如带登录态的页面、评论区)。每次请求现算。
默认情况下,Server Component 里只要有数据库查询,Next.js 通常当作动态处理。但你可以显式控制。
二、Supabase 的缓存陷阱(重点)
本站用 Supabase 客户端查询,Next.js 不会自动帮你缓存这些查询——因为它不知道你查了什么。结果就是:每次请求都真的打数据库。
对评论、阅读量这类变化不频繁的数据,这很浪费。两种解法:
解法 A:给数据打 tag,按需失效
用 Next.js 的 cache + revalidateTag 思路(示意):
import { unstable_cache } from "next/cache"
const getComments = unstable_cache(
async (slug: string) => {
const supabase = await createClient()
return supabase.from("comments").select("*").eq("post_slug", slug)
},
["comments"],
{ revalidate: 60 } // 60 秒内复用缓存
)
数据变了,主动让缓存失效:
import { revalidateTag } from "next/cache"
// 在写操作成功后调用
revalidateTag("comments")
解法 B:路由级重新生成
import { revalidatePath } from "next/cache"
revalidatePath(`/blog/${slug}`) // 让该页下次访问重新渲染
本站还有一个 app/api/revalidate/route.ts(POST)用于外部触发(如 CMS 发布后通知)。
三、强制动态:当你必须「每次都最新」
评论区、用户余额这类必须实时的,要明确告诉 Next.js 别缓存:
import { unstable_noStore as noStore } from "next/cache"
export default async function Page() {
noStore() // 每次都现查数据库
const supabase = await createClient()
// ...
}
记忆口诀:能缓存的缓存(省资源),必须实时的
noStore()(保正确)。
四、客户端还要 SWR 吗?
Server Component 负责「首屏数据」。但用户交互后的局部刷新(如提交评论后不刷新整页就看到新评论)仍然需要客户端方案。
本站这种模式:首屏由 Server Component 渲染,客户端组件用 fetch / Supabase 浏览器客户端做增量更新。SWR、React Query 这类库的价值在于帮你管理「客户端这层」的缓存、loading、重试——它和服务器缓存是两层,别混为一谈。
"use client"
import useSWR from "swr"
const fetcher = (url: string) => fetch(url).then(r => r.json())
export function Comments({ slug }: { slug: string }) {
const { data, isLoading } = useSWR(`/api/comments?post_slug=${slug}`, fetcher)
if (isLoading) return <p>加载中…</p>
return <ul>{data?.map((c: any) => <li key={c.id}>{c.content}</li>)}</ul>
}
五、前端转全栈的缓存心智
| 层 | 谁负责 | 工具 |
|----|--------|------|
| 服务器渲染缓存 | Next.js | unstable_cache / revalidateTag / revalidatePath |
| 数据库查询缓存 | 数据库 / Supabase | 索引、物化视图 |
| 客户端状态缓存 | 浏览器 | SWR / React Query |
核心认知:服务端缓存是为了「省算力、提速」,客户端缓存是为了「交互顺滑」。两者目标不同,要分开设计。
总结
- Server Component 读库默认偏动态,需主动管理缓存。
- 低频变数据用
unstable_cache+revalidateTag;实时数据用noStore()。 - 客户端 SWR/React Query 管的是「交互层」缓存,与服务器缓存是两回事。
- 缓存哲学:能缓存的缓存,必须实时的现查。
下一篇进入 API 的「工程化」——API 设计:zod 校验与统一错误处理,让接口健壮可维护。
练习:给你博客的某个列表页加上
unstable_cache,把revalidate设为 30 秒,观察连续刷新时数据库请求次数(可在 Supabase 日志里看)是否减少。
Comments
Sign in to leave a comment.