前端转全栈 05:服务端数据获取与缓存——既正确又飞快

前端全栈tutorialnextjs缓存data-fetchingrevalidate

系列目录:本文是「前端转全栈: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.