前言
大家好,我是鲫小鱼。是一名不写前端代码
的前端工程师,热衷于分享非前端的知识,带领切图仔逃离切图圈子,欢迎关注我,微信公众号:《鲫小鱼不正经》
。欢迎点赞、收藏、关注,一键三连!!
第十一章 数据缓存策略与 Next.js 运行时
一、理论讲解
1. Next.js 缓存体系全景
Next.js 13+ 引入了更细粒度的缓存体系,主要包括:
- Data Cache:针对 fetch 数据请求的缓存,支持自动失效、手动刷新。
- Route Cache:针对页面路由的缓存,提升 SSR/ISR 性能。
- Full Route Cache:整页缓存,适合静态内容和高并发场景。
- Server Components 缓存:RSC 级别的缓存,结合 Memos 提升渲染效率。
- CDN 协同:结合 CDN 边缘缓存,实现全球加速和高可用。
Data Cache(数据缓存)怎么用?
Data Cache 主要用于缓存 fetch 请求的数据,减少重复请求,提升性能。
ts
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 60 }, // 60秒自动失效
cache: 'force-cache', // 强制缓存
tags: ['dashboard'] // 便于后续按 tag 刷新
});
const data = await res.json();
revalidate
:设置缓存失效时间(秒)。cache: 'force-cache'
:强制缓存(默认行为)。tags
:为缓存打标签,便于后续批量刷新。
Route Cache(路由缓存)怎么用?
Route Cache 用于缓存整个页面的渲染结果,提升 SSR/ISR 性能。
ts
// 页面级别缓存
export const revalidate = 300; // 页面缓存5分钟自动失效
Full Route Cache(整页缓存)怎么用?
Full Route Cache 适合静态内容和高并发场景。只要页面是 SSG/ISR(即用 getStaticProps + revalidate),Next.js 会自动为其生成 Full Route Cache。
- 配合 CDN,静态页面会被缓存到边缘节点,极大提升访问速度。
Server Components 缓存与 Memos 怎么用?
Server Components 支持在服务端缓存组件渲染结果,减少重复渲染。
tsx
// app/components/HeavyChart.server.tsx
import { cache } from 'react';
const getChartData = cache(async (id) => {
// 只会请求一次,后续命中缓存
const res = await fetch(`/api/chart/${id}`);
return res.json();
});
export default async function HeavyChart({ id }) {
const data = await getChartData(id);
return <Chart data={data} />;
}
cache
是 React 18+ 的新特性,配合 Server Components 使用,自动缓存异步函数结果。
缓存失效机制
- 定时失效:通过 revalidate 参数设置自动刷新周期。
- 手动失效:通过 revalidatePath、revalidateTag、API 路由等手动刷新。
- 依赖失效:数据变更时自动失效相关缓存。
revalidatePath / revalidateTag 怎么用?
用于手动失效某个路径或一组带标签的缓存,常用于内容变更后主动刷新页面。
ts
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
export async function POST(req) {
const { path, tag, secret } = await req.json();
if (secret !== process.env.REVALIDATE_SECRET) return new Response('无权限', { status: 401 });
if (path) revalidatePath(path); // 刷新指定路径
if (tag) revalidateTag(tag); // 刷新所有带该 tag 的缓存
return Response.json({ revalidated: true });
}
前端调用示例:
js
await fetch('/api/revalidate', {
method: 'POST',
body: JSON.stringify({ path: '/dashboard', secret: 'xxx' })
});
缓存一致性与安全
- 强一致性:关键业务数据需保证缓存与源数据同步。
- 弱一致性:允许短暂延迟,提升性能。
- 缓存安全:防止缓存穿透、缓存污染、权限数据泄露。
- API 路由涉及缓存刷新时,务必校验权限(如 secret)。
- 用户/角色相关数据不要全局缓存,需按用户粒度缓存或禁用缓存。
ts
if (secret !== process.env.REVALIDATE_SECRET) return new Response('无权限', { status: 401 });
企业级缓存架构
- 多级缓存(内存、磁盘、CDN、数据库)协同
- 监控与报警,自动降级
- 缓存预热与冷启动优化
常见误区
- 只依赖默认缓存,忽略失效策略
- 缓存粒度过粗或过细,导致性能或一致性问题
- 忽略缓存安全,导致数据泄露
二、代码示例
1. fetch 缓存参数与 Data Cache
(见上文 Data Cache 怎么用)
2. 路由与全局缓存控制
(见上文 Route Cache 怎么用)
3. 手动刷新缓存(revalidatePath/revalidateTag)
(见上文 revalidatePath/revalidateTag 怎么用)
4. API 路由缓存与 SSR/ISR 缓存控制
ts
// pages/api/data.ts
export default async function handler(req, res) {
res.setHeader('Cache-Control', 's-maxage=60, stale-while-revalidate');
const data = await fetchData();
res.json({ data });
}
5. 缓存失效与刷新
ts
// 业务数据变更后,主动刷新相关页面/tag
await fetch('/api/revalidate', { method: 'POST', body: JSON.stringify({ path: '/dashboard', secret: 'xxx' }) });
6. Server Components 缓存与 Memos
(见上文 Server Components 缓存与 Memos 怎么用)
7. 缓存监控与错误处理
js
// pages/_app.tsx
import { useEffect } from 'react';
useEffect(() => {
window.addEventListener('error', (e) => {
// 上报缓存相关错误
reportError(e);
});
}, []);
8. 移动端适配
css
@media (max-width: 600px) {
.dashboard { padding: 8px; font-size: 16px; }
}
三、实战项目:仪表盘数据报表组件缓存策略设计
1. 项目需求
- 仪表盘报表数据需高性能、低延迟展示
- 支持多级缓存(Data Cache、Route Cache、CDN)
- 自动失效与手动刷新结合
- 支持缓存监控、错误兜底、极端场景降级
- 移动端体验友好
2. 技术选型
- Next.js 13+ App Router
- fetch Data Cache + revalidateTag
- API 路由缓存
- CDN 边缘缓存
- Sentry/自研埋点监控
3. 目录结构
text
/dashboard-demo
|-- app/
|-- dashboard/
|-- page.tsx
|-- api/
|-- revalidate/
|-- route.ts
|-- components/
|-- Report.tsx
|-- Skeleton.tsx
|-- styles/
|-- globals.css
4. 报表组件实现
ts
// app/dashboard/page.tsx
import Report from '../../components/Report';
import Skeleton from '../../components/Skeleton';
import { useState } from 'react';
export default async function Dashboard() {
const res = await fetch('https://api.example.com/report', {
next: { revalidate: 120, tags: ['report'] },
cache: 'force-cache',
});
const data = await res.json();
return (
<div className="dashboard">
<h1>数据报表</h1>
{data ? <Report data={data} /> : <Skeleton />}
</div>
);
}
ts
// components/Report.tsx
export default function Report({ data }) {
return (
<div className="report">
<h2>核心指标</h2>
<ul>
{data.metrics.map((m) => (
<li key={m.name}>{m.name}: {m.value}</li>
))}
</ul>
</div>
);
}
ts
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
export async function POST(req) {
const { tag, secret } = await req.json();
if (secret !== process.env.REVALIDATE_SECRET) return new Response('无权限', { status: 401 });
revalidateTag(tag);
return Response.json({ revalidated: true });
}
ts
// components/Skeleton.tsx
export default function Skeleton() {
return <div className="skeleton">报表加载中...</div>;
}
css
.skeleton {
background: linear-gradient(90deg, #eee 25%, #f5f5f5 50%, #eee 75%);
background-size: 200% 100%;
animation: skeleton 1.2s infinite linear;
height: 80px;
border-radius: 8px;
margin-bottom: 12px;
}
@media (max-width: 600px) {
.dashboard { padding: 8px; font-size: 16px; }
.skeleton { height: 60px; }
}
四、最佳实践
- 缓存粒度选择:按页面、数据、API 细分缓存,避免全局失效。
ts
fetch(url, { next: { revalidate: 60, tags: ['dashboard'] } });
- 失效策略:结合定时与手动失效,关键数据用 revalidateTag。
- 缓存安全:API 路由校验权限,防止未授权刷新。
ts
if (secret !== process.env.REVALIDATE_SECRET) return new Response('无权限', { status: 401 });
- 团队协作:文档化缓存策略,约定失效流程,自动化测试。
- 监控报警:集成 Sentry/自研埋点,缓存异常自动报警。
- 极端场景降级:接口超时、缓存失效时前端兜底提示。
tsx
if (error) return <div>数据加载失败,请稍后重试</div>;
五、常见问题与解决方案
- Q: 缓存不一致/延迟?
- A: 合理设置 revalidate,结合手动刷新,监控延迟。
- Q: 缓存穿透?
- A: 校验参数,防止无效请求击穿缓存。
- Q: 缓存雪崩?
- A: 缓存失效错峰、分批刷新,避免瞬时高并发。
- Q: 缓存失效慢?
- A: 缩短 revalidate 间隔,结合 on-demand 刷新。
- Q: 数据延迟?
- A: 关键数据用 SSR/ISR,弱一致性数据用缓存。
- Q: 权限问题?
- A: 缓存内容按用户/角色区分,API 校验。
- Q: CDN 缓存未同步?
- A: 检查 CDN 配置,支持 stale-while-revalidate。
- Q: 监控如何做?
- A: 埋点上报缓存命中率、失效、异常。
最后感谢阅读!欢迎关注我,微信公众号:
《鲫小鱼不正经》
。欢迎点赞、收藏、关注,一键三连!!!