Skip to content

渲染篇

前言

渲染分成四大类:

  • CSR: 客户端渲染
  • SSR: 服务器端渲染
  • SSG: 静态站点生成
  • ISR: 服务器端内容更新

CSR(没有源代码显示)

Next.js 支持 CSR,在 Next.js Pages Router 下有两种方式实现客户端渲染。

第一种 use client

use client 页面会显示,但是右键查看源代码不会有渲染

ts
// pages/csr.js
"use client";
import React, { useState, useEffect } from "react";

export default function Page() {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch(
        "https://jsonplaceholder.typicode.com/todos/1"
      );
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      const result = await response.json();
      setData(result);
    };

    fetchData().catch((e) => {
      console.error("An error occurred while fetching the data: ", e);
    });
  }, []);

  return <p>{data ? `Your data: ${JSON.stringify(data)}` : "Loading..."}</p>;
}

第二种 SWR

方法是在客户端使用 NEXT 自己的类库

ts
// pages/csr2.js
import useSWR from "swr";
const fetcher = (...args) => fetch(...args).then((res) => res.json());

export default function Page() {
  const { data, error, isLoading } = useSWR(
    "https://jsonplaceholder.typicode.com/todos/1",
    fetcher
  );

  if (error) return <p>Failed to load.</p>;
  if (isLoading) return <p>Loading...</p>;

  return <p>Your Data: {data.title}</p>;
}

SSR(右键查看源代码)(旧)

这里是回顾以下.新版请看后面章节 顾名思义,渲染工作主要在服务端执行。

Page Router 写一个 demo(旧版本)

ts
// pages/ssr.js
export default function Page({ data }) {
  return <p>{JSON.stringify(data)}</p>;
}

export async function getServerSideProps() {
  const res = await fetch(`https://jsonplaceholder.typicode.com/todos`);
  const data = await res.json();

  return { props: { data } };
}

SSG(基本不用) PageRouter 版本(旧)

简单来说 类似织梦 CMS 那种.现在本地生成好 HTML 页面.要是有改变 就再次重新生成.

适合那种长时间没有数据变化的网站

不获取数据

Next.js 支持 SSG。当不获取数据时,默认使用的就是 SSG。我们使用 Pages Router 写个 demo:

ts
// pages/ssg1.js
function About() {
  return <div>About</div>;
}

export default About;

像这种没有数据请求的页面,Next.js 会在构建的时候生成一个单独的 HTML 文件。

不过 Next.js 默认没有导出该文件。如果你想看到构建生成的 HTML 文件,修改 next.config.js 文件:

ts
const nextConfig = {
  output: "export",
};

module.exports = nextConfig;

再执行 npm run build,你就会在根目录下看到生成的 out 文件夹,里面存放了构建生成的 HTML 文件。

获取数据

  • 第一种情况,页面内容需要获取数据。就比如博客的文章内容需要调用 API 获取。Next.js 提供了 getStaticProps。写个 demo:
ts
// pages/ssg2.js
export default function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

export async function getStaticProps() {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts");
  const posts = await res.json();
  return {
    props: {
      posts,
    },
  };
}

getStaticProps 会在构建的时候被调用,并将数据通过 props 属性传递给页面。

第二种是页面路径需要的数据

这是什么意思呢?就比如数据库里有 100 篇文章,我肯定不可能自己手动定义 100 个路由,然后预渲染 100 个 HTML 吧。Next.js 提供了 getStaticPaths 用于定义预渲染的路径。它需要搭配动态路由使用。写个 demo:

新建 /pages/post/[id].js,代码如下:

ts
// /pages/post/[id].js
export default function Blog({ post }) {
  return (
    <>
      <header>{post.title}</header>
      <main>{post.body}</main>
    </>
  );
}

export async function getStaticPaths() {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts");
  const posts = await res.json();

  const paths = posts.map((post) => ({
    params: { id: String(post.id) },
  }));

  // { fallback: false } 意味着当访问其他路由的时候返回 404
  return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
  // 如果路由地址为 /posts/1, params.id 为 1
  const res = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${params.id}`
  );
  const post = await res.json();

  return { props: { post } };
}

其中,getStaticPaths 和 getStaticProps 都会在构建的时候被调用,getStaticPaths 定义了哪些路径被预渲染,getStaticProps 获取路径参数,请求数据传给页面。

当你执行 npm run build 的时候,就会看到 post 文件下生成了一堆 HTML 文件:

ISR

增量更新在 SSG 基础上

Next.js 支持 ISR,并且使用的方式很简单。你只用在 getStaticProps 中添加一个 revalidate 即可。我们基于 SSG 的示例代码上进行修改:

ts
// pages/post/[id].js
// 保持不变
export default function Blog({ post }) {
  return (
    <>
      <header>{post.title}</header>
      <main>{post.body}</main>
    </>
  );
}

// fallback 的模式改为 'blocking'
export async function getStaticPaths() {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts");
  const posts = await res.json();

  const paths = posts.slice(0, 10).map((post) => ({
    params: { id: String(post.id) },
  }));

  return { paths, fallback: "blocking" };
}

// 使用这种随机的方式模拟数据改变
function getRandomInt(max) {
  return Math.floor(Math.random() * max);
}

// 多返回了 revalidata 属性
export async function getStaticProps({ params }) {
  const res = await fetch(
    `https://jsonplaceholder.typicode.com/posts/${getRandomInt(100)}`
  );
  const post = await res.json();

  return {
    props: { post },
    revalidate: 10, // 10S后开始更新一次
  };
}

revalidate 表示当发生请求的时候,至少间隔多少秒才更新页面。

这听起来有些抽象,以 revalidate: 10 为例,在初始请求后和接下来的 10 秒内,页面都会使用之前构建的 HTML。10s 后第一个请求发生的时候,依然使用之前编译的 HTML。但 Next.js 会开始构建更新 HTML,从下个请求起就会使用新的 HTML。(如果构建失败了,就还是用之前的,等下次再触发更新)

当你在本地使用 next dev 运行的时候,getStaticProps 会在每次请求的时候被调用。所以如果你要测试 ISR 功能,先构建出生产版本,再运行生产服务。也就是说,测试 ISR 效果,用这俩命令:

你可以看到,页面刷新后,文章内容发生变化。然后 10s 内的刷新,页面内容都没有变化。10s 后的第一次刷新触发了更新,10s 后的第二次刷新内容发生了变化。

注意这次 getStaticPaths 函数的返回为 return { paths, fallback: 'blocking' }。它表示构建的时候就渲染 paths 里的这些路径。如果请求其他的路径,那就执行服务端渲染。在上节 SSG 的例子中,我们设置 fallback 为 false,它表示如果请求其他的路径,就会返回 404 错误。

所以在这个 ISR demo 中,如果请求了尚未生成的路径,Next.js 会在第一次请求的时候就执行服务端渲染,编译出 HTML 文件,再请求时就从缓存里返回该 HTML 文件。SSG 优雅降级到 SSR。