Next.js 的 pre-rendering 实作是这个框架的一大卖点,在 Next.js 中的 pre-rendering 有两种实作方法,一个是 Server Side Rendering,另一个是 Static Side Generation。
在这篇文章中我们要谈的是 Static Side Generation,Static Side Generation 指的是在打包阶段会将所有渲染所需要的资料都准备好,包括呼叫 API 的资料,最後会将资料都嵌入到 HTML 档案之中,因此使用者在浏览网站时就会直接拿到已经渲染完的 HTML 静态档案。
Next.js 提供了一个 function — getStaticProps
,它可以自动地在程序码打包阶段自动执行执行上述的流程,我们不必做过多的设定就可以撰写 pre-rendering 的程序码。
getStaticProps
它是一个写在 React component 外的 function,必须以 export
的形式定义它,而且同时这个 function 也要是 async
的。加上 async
一个很大的好处是,在 getStaticProps
里面就可以写 await
,在呼叫 API 时就可以利用这个特性撰写。
而 getStaticProps
会在打包阶段自动执行,并将 props
传入到 component 中,可以用於渲染 React 中的内容。
export async function getStaticProps(context) {
return {
props: {},
};
}
以下这个范例是在 getStaticProps
中呼叫一个 REST API,从 API 拿到贴文 (Post) 的资料後,传入到 component 中,并渲染 title
与 body
到画面上。这个范例我们在前面章节深入浅出 CSR、SSR 与 SSG 也有提到过。
import { GetStaticProps } from "next";
interface Post {
userId: number;
id: number;
title: string;
body: string;
}
interface HomeProps {
post: Post;
}
export default function Home({ post }: HomeProps) {
return (
<div>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
);
}
export const getStaticProps: GetStaticProps = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/posts/1");
const post: Post = await res.json();
return {
props: {
post,
},
};
};
如果上述范例的档案位置在 pages/home.tsx
,Next.js 会在 next build
的阶段为符合的页面产生 .html
档案:
.next/server/pages/home.html
getStaticPaths
Dynamic routes 可以匹配近乎是无上限的 pattern,而每一个 pattern 如果在 next build
都要对应到一个页面,这样不是会产生无上限的 HTML 档案吗?
对於 dynamic routes,Next.js 有相对应的解决方案,也就是使用 getStaticPaths
事先定义哪些页面需要产生 HTML 档案。
语法跟 getStaticProps
很像,皆是在 component 外面定义一个 async
的 function,名称即是 getStaticPaths
,回传值包含两个 key,分别是 paths
与 fallback
。
export async const getStaticPaths: GetStaticPaths = () => {
return {
paths: [
{ params: { ... } }
],
fallback: boolean
};
}
paths
paths
这个参数将会决定 dynamic routes 有哪些页面将会产生 HTML 档案,例如以在 file-based routing 中我们用到的 pages/products/[id].tsx
,我们可以这样定义:
return {
paths: [
{ params: { id: '1' } },
{ params: { id: '2' } }
],
fallback: ...
}
以上定义了两个 id
,所以就会针对两个页面 /products/1
与 /products/2
生成 HTML 档案。
而 dynamic routes 还包括很多种的定义方式:
多层次的定义 pages/posts/[year]/[month]/[day].tsx
,所以在每一个 params
中就要包含 year
、 month
与 day
三个 key:
return {
paths: [
{ params: { year: '2021', month: '7', day: '24' } },
{ params: { year: '2021', month: '9', day: '28' } }
],
fallback: ...
}
catch all routes 的定义方式 pages/posts/[...date].tsx
,而 date
在 router.query
会是 array 的型别,所以在 params
中定义 date
也要是 array 的型别:
return {
paths: [
{ params: { date: ['2021', '7', '24'] } },
{ params: { date: ['2021', '9', '28'] } }
],
fallback: ...
}
还有一种是 optional catch all rotues,例如 pages/posts/[[...date]].tsx
就可以匹配 /posts
、 /posts/123
、 /posts/2021/7/24
多种的路径,一般情况可以传入 array 定义路径,但是也可以使用 null
、 []
、 undefined
或 false
多种不同的方式,让 Next.js 打包时只产生 /
的页面:
return {
paths: [
{ params: { date: false } },
],
fallback: ...
}
fallback
fallback
允许传入三种值,分别为 true
、 false
与 'blocking'
,以下是 Next.js 中的型别定义:
type GetStaticPathsResult<P extends ParsedUrlQuery = ParsedUrlQuery> = {
paths: Array<string | { params: P; locale?: string }>;
fallback: boolean | "blocking";
};
fallback: false
时fallback
为 false
的行为很单纯,意思是说当使用者浏览没有定义在 getStaticPaths
中的页面时,会回传 404 的页面。
例如在 pages/products/[id].tsx
中的 getStaticPaths
定义以下的回传值,所以 Next.js 只会产生 /products/1
与 /products/2
两个路由相对应的页面。而使用者如果浏览了 /products/3
,他将会收到 404 的页面。
return {
paths: [{ params: { id: "1" } }, { params: { id: "2" } }],
fallback: false,
};
所以, fallback: false
比较适合用在较为静态的网站,例如部落格、较小的产品型录网页等,只有等网页的管理者新增内容时,重新让 Next.js 打包後,才会有新的页面产生。
fallback: true
时使用 fallback: true
的使用比较复杂一点,因为与 fallback: false
不同的点在於,当使用者浏览没有在 getStaticPaths
中定义的页面时,Next.js 并不会回应 404 的页面,而是回应 fallback 的页面给使用者。
这个流程会呼叫 getStaticProps
,在服务器产生资料前,使用者浏览的是 fallback 的页面,在 getStaticProps
执行完後,同样由 props
注入资料到网页中,使用者这时就能看到完整的页面。
而经过这个流程的页面,该页面会被加入到 pre-rendering 页面中,下次如果再有同样页面的请求时,服务器并不会再次的重新呼叫 getServerSideProps
,产生新的页面,而是回应已经产生的页面给使用者。
使用前几个章节用到的产品详细介绍页面,由於在 getStaticPaths
中的 id
只有 '1'
,所以在 next build
阶段只会生成 /products/1
这个页面的 HTML,但是在设定 fallback: true
的情况下,当一位使用者浏览 /products/2
时, Next.js 会做以下几件事情:
router.isFallback
会一直为 true
,因次可以用条件式渲染 loading 情况下的页面,而这时从 props
中拿到的 product
是 undefined
。import { GetStaticPaths, GetStaticProps } from "next";
import { useRouter } from "next/router";
import { getProductById, Product as ProductType } from "../../fake-data";
import ProductCard from "../../components/ProductCard";
import { PageTitle, ProductContainer } from "./[id].style";
interface ProductProps {
product: ProductType;
}
const Product = ({ product }: ProductProps) => {
const router = useRouter();
if (router.isFallback) {
return <div>Loading...</div>;
}
return (
<>
<PageTitle>商品详细页面</PageTitle>
<ProductContainer>
<ProductCard product={product} all />
</ProductContainer>
</>
);
};
export const getStaticProps: GetStaticProps = async ({ params }) => {
const product = getProductById(params?.id as string);
return {
props: {
product,
},
};
};
export const getStaticPaths: GetStaticPaths = async () => {
return {
paths: [{ params: { id: "1" } }],
fallback: true,
};
};
export default Product;
在使用 fallback: true
的时候,使用者第一次浏览网页时,收到的会是在 router.isFallback
底下渲染的资料:
在第二次浏览同一个页面之後,该页面因为先前已经生成过,所以可以直接回应 HTML 给使用者,在原始码中就可以看到完整的资料。
各位读者可以注意到,因为是 SSG 的关系, fallback: true
实际上是真的会产生 HTML 在资料夹中,例如使用者如果浏览不存在的 /products/2
,Next.js 就会动态地生成新的 HTML:
.next/server/pages/prodcuts/2.html
fallback: 'blocking'
时在 getStaticPaths
使用这个设定时,跟 fallback: true
一样,在使用者浏览不存的页面时,服务器不会回传 404 的页面,而是执行 getStaticProps
,走 pre-rendering 的流程。
但是与 fallback: true
不一样的点在於没有 router.isFallback
的状态可以使用,而是让页面卡在 getStaticProps
的阶段,等待执行完後回传结果给使用者。
所以使用者体验会非常像似 getServerSideProps
,但优点是下次使用者再次浏览同一个页面时,服务器可以直接回传已经生成的 HTML 档案,往後甚至可以藉由 CDN 的 cache 提升页面的载入速度。
<<: Day22 - Ajax 加上 Antiforgery Token (二)
现在有资料,只差介面了。 建立 base-window 组件 虽然每个视窗功能都不同,但是视窗外框功...
Conclusion 呼~到今天为止 9 天过去了,Libraries 之间的比较篇章也到今天告一...
钓鱼游戏 教学原文参考:钓鱼游戏 这篇文章会介绍,如何在 Scratch 3 里使用多个角色、函式、...
PostgreSQL是一个开源的框架,关联式资料库资管理系统。PostgreSQL 的操作过程相似於...
在工作中或多或少都会遇到空白行存在的情况。如果只有几个空白行,那麽手动轻松删除即可,但是遇到100行...