渲染模式
从我们关于服务器端渲染(SSR)的讨论中得知,较高的请求处理会影响首字节时间(TTFB)。同样地,客户端渲染(CSR)中,较大的 JavaScript 包会因下载和处理脚本所需的时间而对应用的首次内容绘制(FCP)、最大内容绘制(LCP)和交互时间(TTI)产生不利影响。
静态生成(SSG)试图通过在网站构建时预先渲染 HTML 内容来解决这些问题。
此处为语雀视频卡片,点击链接查看:static-generation-1.webm
每个用户访问的路由都会提前生成一个静态 HTML 。这些静态 HTML 文件可能存储在服务器或CDN上,在客户端请求时获取。
此处为语雀视频卡片,点击链接查看:static-generation-2.webm
静态文件还可以被缓存,从而提供更大的弹性。由于 HTML 是提前生成的,服务器上的处理时间可以忽略不计,从而实现更快的首字节时间(TTFB)。在理想情况下,客户端的 JavaScript 应该最少,静态页面在被客户端接收后应尽快变得可交互。
基本结构
顾名思义,静态渲染适用于静态内容,页面无需根据登录用户进行自定义(例如个性化推荐)。因此,网站的"关于我们"、"联系我们"、博客页面或电子商务应用的产品页面等静态页面是静态渲染的理想选择。像 Next.js、Gatsby 和 VuePress 这样的框架支持静态生成。
让我们从这个简单的 Next.js 示例开始。
Next.js:
jsx
// pages/about.js
export default function About() {
return (
<div>
<h1>关于我们</h1>
{/* ... */}
</div>
);
}
当网站构建(使用 next build
)时,此页面将被预渲染成一个 HTML 文件 about.html
,可通过路由 /about
访问。
带数据的 SSG
像"关于我们"这样的静态内容可能无需获取数据即可渲染。然而,对于个人博客页面,需要将数据与特定模板合并,然后在构建时渲染成 HTML。
生成的 HTML 页面数量取决于博客文章的数量。这些场景可以使用 Next.js 静态渲染来解决。我们可以根据可用项目生成列表页面。让我们看看如何操作。
列表页面 - 所有Items
生成列表页面是一个内容依赖于数据的场景。数据在构建时从数据库获取来构建页面。在 Next.js 中,这可以通过在页面组件中导出 getStaticProps()
函数来实现。该函数在服务器上调用获取数据。然后数据可以传递给页面的 props
。以下是生成产品列表页面的代码,最初分享在这篇文章。
jsx
// 此函数在构建服务器上于构建时运行
export async function getStaticProps() {
return {
props: {
products: await getProductsFromDatabase(),
},
};
}
// 页面组件在构建时从 getStaticProps 接收 products prop
export default function Products({ products }) {
return (
<>
<h1>产品</h1>
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
</>
);
}
该函数不会包含在客户端 JavaScript 中,因此可以直接从数据库获取数据。
个人详情页面 - 每个Item
在上述示例中,我们在列表页面上为每个产品提供一个详细的个人页面。这些页面可以通过点击列表页面访问。
假设我们有产品 ID 为 101
、102
、103
等的产品。我们需要它们的路由是 /products/101
、/products/102
、/products/103
。为了在 Next.js 中实现这一点,我们可以结合使用 getStaticPaths()
函数和动态路由。
我们需要为此创建一个通用的页面组件 products/[id].js
,并在其中导出 getStaticPaths()
函数。该函数将返回所有可能的产品 ID,这些 ID 用于在构建时预渲染个人产品页面。以下 Next.js 代码显示了如何构建此代码。
jsx
// pages/products/[id].js
// 在 getStaticPaths() 中,你需要返回希望在构建时预渲染的产品页面(/products/[id])的 ID 列表。为此,可以从数据库获取所有产品。
export async function getStaticPaths() {
const products = await getProductsFromDatabase();
const paths = products.map((product) => ({
params: { id: product.id },
}));
// fallback: false 表示没有正确 ID 的页面将显示 404 错误。
return { paths, fallback: false };
}
// params 将包含每个生成页面的 ID。
export async function getStaticProps({ params }) {
return {
props: {
product: await getProductFromDatabase(params.id),
},
};
}
export default function Product({ product }) {
// 渲染产品
}
产品页面的详细信息可以通过产品 ID 的 getStaticProps
函数在构建时填充。注意这里使用了 fallback: false
。它表示如果特定路由或产品 ID 没有对应的页面,将显示 404 错误页面。
因此,我们可以使用 SSG 预渲染多种不同类型的页面。
SSG - 关键考虑因素
如前所述,SSG 通过减少客户端和服务器所需的处理,为网站带来了出色的性能。由于内容已经存在且可以被网络爬虫轻松渲染,这些网站还对搜索引擎优化(SEO)友好。尽管 SSG 是一个出色的渲染模式,但在评估 SSG 对特定应用的适用性时,需要考虑以下因素。
- 大量的 HTML 文件: 需要为用户访问的每个路由生成单独的 HTML 。例如,当将其用于博客时,每篇博客文章都会生成一个 HTML 文件。对任何文章的编辑都需要重新构建才能更新。维护大量的 HTML 文件可能具有挑战性。
- 托管依赖: 为了使 SSG 网站响应迅速,用于存储和托管平台也必须出色。如果一个 SSG 网站托管在多个 CDN 上,利用边缘缓存,那么可以实现卓越的性能。
- 动态内容: 每当内容发生变化时,SSG 网站都需要重新构建和重新部署。如果在内容更改后网站未重新构建,显示的内容可能会过时。这使得 SSG 不适合高度动态的内容。