你的数据该在哪儿拿?Next.js三种姿势一次讲清
Next.js给了你三把“钥匙”去开门拿数据。选错了,要么页面慢得像蜗牛,要么用户数据混乱,要么服务器天天崩。别怕,其实规则很简单:数据变不变?要不要实时?要不要SEO? 回答了这三个问题,答案就出来了。 我们用“开餐厅”来打个比方: 三种方式对应三种数据需求。 适用场景:数据基本不变,或者你可以接受一定延迟更新。比如博客文章、产品介绍页、帮助文档。 怎么做:在构建时( 优点:快(CDN缓存)、省服务器资源、SEO完美。 生活比喻:预制菜包。你提前做好,客人来了热一下就能吃。适合麦当劳(每家的巨无霸都一样)。 适用场景:数据随用户不同而变化,或者数据实时性要求极高。比如用户个人主页、购物车、实时搜索页。 怎么做:每次请求都跑到服务器上执行 优点:数据永远最新,可以读取请求上下文(cookies、headers),适合个性化内容。 生活比喻:餐厅里的大厨现炒。客人点一份炒一份,味道新鲜,但慢一点,厨师也累。 适用场景:数据实时性要求高,但SEO不重要,或者你不希望服务器压力大。比如用户仪表盘、图表数据、实时消息列表。 怎么做:在组件里用 优点:服务器负担小(只提供API),可以实时更新,适合交互密集的后台。 生活比喻:扫码点餐。客人自己看手机菜单,下单后厨房再做。菜单可以随时改,但客人得先扫个码(等待JS加载)。 Next.js还提供了一个“中间态”: 适合数据偶尔变化,但又不希望每次请求都重新生成的场景,比如电商的商品库存(每小时更新就行)。 数据是否需要实时(每次请求都要最新)? 数据是否依赖用户身份(cookies/headers)? 简单口诀: 假设首页包含:顶部最新公告(实时)、中间产品列表(每天更新一次)、底部用户推荐(个性化)。 但注意: 更佳实践:产品列表单独做一个静态页面,首页通过客户端请求该接口。 掌握这三种,你就能游刃有余地驾驭Next.js的数据层。别再每页都用客户端fetch了,下次老板说页面慢,你就能有理有据地告诉他:“这个页面应该用ISR!”前言
一、
getStaticProps:预制菜,又快又省next build)获取数据,生成静态HTML。之后每次请求直接返回这个HTML,速度极快,还能放CDN。// pages/posts/[id].js
export async function getStaticPaths() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
const paths = posts.map(post => ({ params: { id: post.id } }));
return { paths, fallback: false };
}
export async function getStaticProps({ params }) {
const post = await fetch(`https://api.example.com/posts/${params.id}`).then(r => r.json());
return {
props: { post },
revalidate: 60, // 增量静态再生(ISR):60秒后重新生成,不是必须
};
}
缺点:构建时数据必须是可得的;如果有大量页面,构建时间会变长(可用fallback: true或ISR缓解)。二、
getServerSideProps:现点现做,永远新鲜getServerSideProps,获取数据后渲染成HTML返回。// pages/profile.js
export async function getServerSideProps(context) {
const { req } = context;
const token = req.cookies.token;
const user = await fetch('https://api.example.com/user', {
headers: { Authorization: `Bearer ${token}` }
}).then(r => r.json());
return { props: { user } };
}
缺点:每个请求都调用服务器,性能比静态生成差,不能放CDN。三、客户端获取(CSR):扫码点餐,不占服务资源
useEffect + fetch,或者用SWR、React Query等库。import { useState, useEffect } from 'react';
export default function Dashboard() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/dashboard')
.then(r => r.json())
.then(setData);
}, []);
if (!data) return <div>加载中...</div>;
return <div>{/* 渲染数据 */}</div>;
}
缺点:首次加载有白屏,SEO不友好(因为内容靠JS填充)。四、混合模式:ISR(增量静态再生)
revalidate 参数。你依然用getStaticProps,但指定一个秒数,超过这个时间后,下次请求会尝试重新生成页面,同时返回旧版本。这样既有静态的速度,又能定期更新。return { props: { data }, revalidate: 3600 }; // 每小时更新一次五、怎么选?决策树
getServerSideProps;否 → 客户端获取。getServerSidePropsgetStaticProps(甚至可以ISR)getStaticProps。getServerSideProps。六、实战:一个混合使用的首页
getServerSideProps(实时,且SEO重要?其实公告可以客户端获取,但为了体验,可以SSR)。getStaticProps + revalidate: 86400(一天更新一次)。export default function Home({ announcement, products }) {
const [recommendations, setRecommendations] = useState([]);
useEffect(() => {
fetch('/api/recommendations').then(r => r.json()).then(setRecommendations);
}, []);
return (
<>
<Announcement data={announcement} />
<ProductList data={products} />
<Recommendations data={recommendations} />
</>
);
}
export async function getServerSideProps() {
const announcement = await fetchAnnouncement(); // 实时
const products = await fetchProducts(); // 缓存一小时
return {
props: { announcement, products },
revalidate: 3600, // 对products有效,对announcement无效(因为SSR总是实时)
};
}getServerSideProps里不能同时用revalidate(它只对静态生成有效)。如果你要混合,可以把产品数据用getStaticProps单独抽出来,或者全部在客户端获取。上面代码只是示意:实际中getServerSideProps的返回值里加revalidate是无意义的。七、总结:没有银弹,只有合适