关于Client Component的获取数据方式
Server Actions
优点
- TypeScript 类型安全,完美契合 Next.js 的全栈开发模式
- 天然支持服务端数据操作,无需暴露 API 端点
缺点
- 本质是 POST 请求,多个数据源会形成瀑布流请求链,显著降低页面加载速度
- 官方推荐仅用于数据变更操作(如表单提交),而非数据获取
传统 Fetch + useEffect
- 需手动处理缓存策略、错误边界、加载状态管理
- 易出现竞态条件(Race Conditions)和内存泄漏问题
- 缺少自动重试机制,代码冗余度高
使用第三方库如React Query, SWR
// 结合 Server Actions 的类型安全优势 const { data } = useQuery({ queryKey: ['todos'], queryFn: () => fetchFromServerAction() }) // SWR 自动重试示例 const { data, error } = useSWR('/api/data', fetcher, { onErrorRetry: (error, key, config, revalidate, { retryCount }) => { if (retryCount >= 3) return setTimeout(() => revalidate(), 5000) } })
我的感想是如果页面上只有单一的数据源,直接使用server action就行,如果有多个数据源,就是使用第三方库来获取数据,但是好像有第三方库配合server actions的用法,既能保证parallel数据获取,又可以保证type safe,有时间研究一下
关于Static Rendering
如果想要在build time预渲染好dynamic routing的页面可以使用generateStaticParams
当然前提是页面必须为server component
// app/blog/[slug]/page.tsx export async function generateStaticParams() { const posts = await fetch('https://.../posts').then(res => res.json()) return posts.map(post => ({ slug: post.slug, })) } export default async function Page({ params }: { params: { slug: string } }) { const post = await fetchPost(params.slug) return <Article content={post.content} /> }
Tailwind 深色模式
可以通过css variable使用Semantic naming的方式来定义浅色和深色模式对应的颜色
这样就不用写”dark: XXXX”
/* globals.css */ :root { --primary-bg: hsl(0, 0%, 100%); /* 浅色模式背景 */ --primary-text: hsl(240, 10%, 15%); /* 浅色模式文字 */ } .dark { --primary-bg: hsl(240, 10%, 15%); /* 深色模式背景 */ --primary-text: hsl(0, 0%, 100%); /* 深色模式文字 */ }
现在写组件方便多了
<div class="bg-primary-bg text-primary-text"> 管你深色浅色,我只认 semantic name! </div>
Next-Theme
一般使用next-theme来把用户选择的主题保存在local storage
创建 Client Component 包裹
ThemeProvider
// providers/theme-provider.tsx 'use client' import { ThemeProvider as NextThemeProvider } from 'next-themes' export function ThemeProvider({ children }: { children: React.ReactNode }) { return ( <NextThemeProvider attribute="class" storageKey="theme-preference" enableSystem={false} > {children} </NextThemeProvider> ) } // 根布局配置 <html lang="en" suppressHydrationWarning> <body> <ThemeProvider>{children}</ThemeProvider> </body> </html>
把rootlayout包住,然后在html tag上加上suppressHydrationWarning来阻止hydration报错
suppressHydrationWarning
:因为主题从 localStorage 读取(浏览器 API),服务端无法预知,SSR 阶段必然导致 HTML 与客户端不一致。这个属性让 React 闭嘴别报警告。
attribute="class"
:让 next-theme 通过修改<html>
的 class(dark
/light
)切换主题,正好对接我们的 CSS 变量方案。
Server Component的滥用
感觉我陷入了一个误区,不管什么page都要写成server component,它虽然好,但是一旦涉及到state management就吃大亏,比如要实现一个搜索框,server component虽然也能实现,通过读取url的参数来获取query,但是这样就等于多写了一个页面,如果想要实现例如弹出一个搜索框modal,只能老老实实写client component
所以一旦涉及数据实时更新,很多的数据交互,还是得用client component才可以获得更好的UX还有DX的体验,像是博客这种全是静态页面的,就是server component大展身手的时候了
🚫 Server Component 禁区
- 任何需要
useState
/useEffect
的交互逻辑
- 事件监听(点击、滚动、表单输入)
- 需要浏览器 API 的功能(弹窗、动画、localStorage)
✅ Server Component 快乐屋
- 纯静态内容(博客、文档、产品介绍页)
- 重度依赖后端数据的页面(电商列表、仪表盘)
- SEO 关键路径(用服务器数据生成完整 HTML)
正确姿势
最终把组件拆成「双胞胎模式」:
// app/articles/page.tsx(Server Component) export default async function Page({ searchParams }: { searchParams?: { q?: string } }) { // 服务端获取数据(SEO 友好) const initialData = await fetchArticles(searchParams?.q) return ( // 把交互逻辑甩锅给 Client Component <ArticlesClientComponent initialData={initialData} /> ) }
// components/articles-client.tsx(Client Component) 'use client' export function ArticlesClientComponent({ initialData }: { initialData: Article[] }) { // 客户端状态管理 const [searchQuery, setSearchQuery] = useState('') const [showFilterModal, setShowFilterModal] = useState(false) // 优雅的实时搜索(防抖 + 客户端过滤) const filteredData = useMemo(() => ( initialData.filter(item => item.title.includes(searchQuery)) ), [searchQuery]) return ( <> <input value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder="实时搜索..." /> <button onClick={() => setShowFilterModal(true)}> 高级筛选 </button> {/* 弹窗自由! */} {showFilterModal && ( <FilterModal onClose={() => setShowFilterModal(false)} /> )} {filteredData.map(article => <ArticleCard key={article.id} />)} </> ) }
Edge Runtime
最新消息Next.js版本15.2支持了Middleware配置Node.js Runtime
事情是这样的...
最近给 Next.js 项目加鉴权中间件,想着直接在 middleware 里查数据库拿用户 session 多优雅。结果代码一跑就报错,定睛一看:
Error: PrismaClient can't run on Edge Runtime!
当场傻眼——原来 Next.js 的 middleware 默认跑在 Edge Runtime,只支持部分API,看了看GitHub Issues也有很多开发者抱怨何时支持nodejs run time
临时抱佛脚的土方法
搞了个曲线救国方案:
1️⃣ 先写个走 Node.js Runtime 的 API 端点查数据库
// 这个藏在 /pages/api/session.ts 里 export default async function handler() { const session = await prisma.session.findUnique(...) return res.json(session) // 当个无情的传话筒 }
2️⃣ 在 middleware 里厚着脸皮调自己的 API
// middleware.ts 里的骚操作 const session = await fetch('/api/session').then(r => r.json())
虽然能用,但每次鉴权都要绕路调接口,性能直接扑街,代码看着也像打补丁。
后来。。。
后来发现原来Prisma支持Edge Runtime,只要。。
// 旧代码(会报错) import { PrismaClient } from '@prisma/client' // 新代码(原地复活!) import { PrismaClient } from '@prisma/client/edge'
Session Token/JWT token安全性
1. HTTP-only Cookie 防篡改机制
将 Session Token 或 JWT Token 存储在 HTTP-only Cookie 中,确保浏览器端的 JavaScript 无法通过
document.cookie
读取或修改。此设计直接阻断 XSS 攻击窃取凭据的路径。2. Session Token 的服务器端验证逻辑
- 服务端存储:Session Token 的完整数据(如用户 ID、权限、过期时间)存储在服务端数据库(如 Redis),仅向客户端返回 Session ID,且这个ID很长,不可能通过暴力破解猜到。
- 防篡改验证:客户端每次携带 Session ID 请求时,服务端会校验该 ID 是否存在有效关联数据。若 ID 被篡改(如伪造随机 UUID),服务端因无对应数据而直接拒绝请求。
3. JWT Token 的密码学签名验证
JWT 由三部分构成(以
.
分隔):- Header:声明令牌类型(
typ: "JWT"
)和签名算法(如alg: HS256
),经 Base64Url 编码。
- Payload:存储声明(Claims),如用户身份(
sub
)、过期时间(exp
)等业务数据,经 Base64Url 编码。
- Signature:对
Base64Url(Header).Base64Url(Payload)
的原始二进制数据,使用服务端私钥按 Header 声明的算法生成签名。
篡改防御原理:
若攻击者修改 Header 或 Payload 内容(如将
role: "user"
改为 role: "admin"
),必须重新生成合法签名。由于缺乏服务端私钥(HS256)或无法破解非对称加密(如 RS256),新生成的签名与服务端验签结果必然不匹配,导致令牌失效。方案 | 存储位置 | 防篡改机制 | 失效控制 | 性能 |
Session | 服务端数据库 | 篡改不了一点 | 服务端主动清除/手动Revoke | 依赖于服务器性能,使用Redis可显著提高性能 |
JWT | 客户端 Cookie | 签名验证 | 依赖过期时间 | 高 |