前言

Next.js给了你三把“钥匙”去开门拿数据。选错了,要么页面慢得像蜗牛,要么用户数据混乱,要么服务器天天崩。别怕,其实规则很简单:数据变不变?要不要实时?要不要SEO? 回答了这三个问题,答案就出来了。

我们用“开餐厅”来打个比方:

  • 静态生成(SSG):菜单印在纸上,顾客看的是纸质菜单。印刷一次管好几天,永不变化。
  • 服务端渲染(SSR):电子黑板,每次客人来,服务员现写菜单,保证最新。
  • 客户端渲染(CSR):客人扫码点餐,手机上的菜单是动态加载的。

三种方式对应三种数据需求。

一、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秒后重新生成,不是必须
  };
}

优点:快(CDN缓存)、省服务器资源、SEO完美。
缺点:构建时数据必须是可得的;如果有大量页面,构建时间会变长(可用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 } };
}

优点:数据永远最新,可以读取请求上下文(cookies、headers),适合个性化内容。
缺点:每个请求都调用服务器,性能比静态生成差,不能放CDN。

生活比喻:餐厅里的大厨现炒。客人点一份炒一份,味道新鲜,但慢一点,厨师也累。

三、客户端获取(CSR):扫码点餐,不占服务资源

适用场景:数据实时性要求高,但SEO不重要,或者你不希望服务器压力大。比如用户仪表盘、图表数据、实时消息列表。

怎么做:在组件里用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>;
}

优点:服务器负担小(只提供API),可以实时更新,适合交互密集的后台。
缺点:首次加载有白屏,SEO不友好(因为内容靠JS填充)。

生活比喻:扫码点餐。客人自己看手机菜单,下单后厨房再做。菜单可以随时改,但客人得先扫个码(等待JS加载)。

四、混合模式:ISR(增量静态再生)

Next.js还提供了一个“中间态”:revalidate 参数。你依然用getStaticProps,但指定一个秒数,超过这个时间后,下次请求会尝试重新生成页面,同时返回旧版本。这样既有静态的速度,又能定期更新。

return { props: { data }, revalidate: 3600 }; // 每小时更新一次

适合数据偶尔变化,但又不希望每次请求都重新生成的场景,比如电商的商品库存(每小时更新就行)。

五、怎么选?决策树

  1. 数据是否需要实时(每次请求都要最新)?

    • 是 → 是否需要SEO?是 → getServerSideProps;否 → 客户端获取。
    • 否 → 进入2
  2. 数据是否依赖用户身份(cookies/headers)?

    • 是 → getServerSideProps
    • 否 → getStaticProps(甚至可以ISR)
  3. 数据量巨大且变化频繁,但不需要SEO? → 客户端获取。

简单口诀

  • 博客文章、产品介绍:getStaticProps
  • 用户个人中心、购物车:getServerSideProps
  • 后台图表、实时看板:客户端获取。

六、实战:一个混合使用的首页

假设首页包含:顶部最新公告(实时)、中间产品列表(每天更新一次)、底部用户推荐(个性化)。

  • 公告:getServerSideProps(实时,且SEO重要?其实公告可以客户端获取,但为了体验,可以SSR)。
  • 产品列表:getStaticProps + revalidate: 86400(一天更新一次)。
  • 用户推荐:客户端获取(依赖登录状态,且SEO不重要)。
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是无意义的。

更佳实践:产品列表单独做一个静态页面,首页通过客户端请求该接口。

七、总结:没有银弹,只有合适

  • getStaticProps:适合“快、不变、要SEO”。
  • getServerSideProps:适合“实时、要SEO、可接受稍慢”。
  • 客户端获取:适合“实时、不要SEO、降低服务器压力”。

掌握这三种,你就能游刃有余地驾驭Next.js的数据层。别再每页都用客户端fetch了,下次老板说页面慢,你就能有理有据地告诉他:“这个页面应该用ISR!”

标签: none

添加新评论