静态生成

根据我们对 SSR 的讨论,知道服务器上较长的请求处理时间会对 TTFB 产生负面影响。

类似地,对于 CSR,由于下载和处理脚本所花费的时间,大型 JavaScript 包可能对应用程序的 FCP、LCP 和 TTI 有害。

静态呈现或静态生成 (SSG) 试图通过将预呈现的 HTML 内容交付给在构建站点时生成的客户端来解决这些问题。

每个用户可以访问的路由对应的静态HTML文件是提前生成的。 这些静态 HTML 文件可能在服务器或 CDN 上可用,并在客户端请求时获取。

静态文件也可以被缓存,从而提供更大的弹性。

由于 HTML 响应是预先生成的,服务器上的处理时间可以忽略不计,从而导致更快的 TTFB 和更好的性能。

在理想的情况下,客户端 JS 应该是最少的,并且静态页面应该在客户端收到响应后立即变得可交互。

因此,SSG 有助于实现更快的 FCP/TTI。


SSG - 基本结构

顾名思义,静态渲染非常适合静态内容,其中页面不需要根据登录用户进行定制(例如个性化推荐)。

因此,像“关于我们”、“联系我们”、网站的博客页面或电子商务应用程序的产品页面等静态页面是静态渲染的理想选择。

Next.js、Gatsby 和 VuePress 等框架支持静态生成。

让我们从这个没有任何数据的静态内容渲染的简单 Next.js 示例开始。

Next.js:

1
2
3
4
5
6
7
8
9

// pages/about.js

export default function About() {
 return <div>
   <h1>About Us</h1>
   {/* ... */}
 </div>
}

当站点被构建(使用 Next.js 构建)时,这个页面将被预渲染成一个 HTML 文件 about.html,可在 /about 路径访问。


有数据的 SSG

“关于我们”或“联系我们”页面中的静态内容可能会按原样呈现,而无需从数据存储中获取数据。

但是,对于单个博客页面或产品页面等内容,来自数据存储的数据必须与特定模板合并,然后在构建时呈现为 HTML。

生成的 HTML 页面的数量将分别取决于博客文章的数量或产品的数量。

要访问这些页面,您可能还有列表页面,这些页面将是包含分类和格式化的数据项列表的 HTML 页面。

这些场景可以使用 Next.js 静态渲染来解决。

我们可以根据可用项目生成列表页面或单个项目页面。 让我们看看如何。

列表页面 - 所有项目

列表页面的生成是页面上显示的内容依赖于外部数据的场景。

这些数据将在构建时从数据库中获取以构建页面。

在 Next.js 中,这可以通过在页面组件中导出函数 getStaticProps() 来实现。

该函数在构建服务器上的构建时被调用以获取数据。

然后可以将数据传递给页面的 props 以预渲染页面组件。

让我们看一下生成产品列表页面的代码,该页面最初是作为本文的一部分共享的。



// 此函数在构建时在构建服务器上运行
export async function getStaticProps() {
 return {
   props: {
     products: await getProductsFromDatabase()
   }
 }
}

// 页面组件在构建时从 getStaticProps 接收 products 属性
export default function Products({ products }) {
 return (
   <>
     <h1>Products</h1>
     <ul>
       {products.map((product) => (
         <li key={product.id}>{product.name}</li>
       ))}
     </ul>
   </>
 )
}

该函数不会包含在客户端 JS 包中,因此甚至可以用于直接从数据库中获取数据。

个人详情页面 - 每项

在上面的示例中,我们可以为列表页面上列出的每个产品创建一个单独的详细页面。

这些页面可以通过点击列表页面上的相应项目或直接通过一些其他途径来访问。

假设我们有产品 ID 为 101,102 103 的产品,依此类推。
我们需要在路由 /products/101、/products/102、/products/103 等处提供他们的信息。

为了在 Next.js 的构建时实现这一点,我们可以将函数 getStaticPaths()动态路由结合使用。

我们需要为此创建一个公共页面组件 products/[id].js 并导出其中的函数 getStaticPaths()。

该函数将返回所有可能的产品 ID,这些 ID 可用于在构建时预呈现单个产品页面。

此处可用的以下 Next.js 框架显示了如何为此构建代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31


// pages/products/[id].js

// 页面/产品/[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 }) {
 // Render product
}

产品页面上的详细信息可以在构建时使用特定产品 ID 的 getStaticProps 函数填充。

注意这里使用 fallback: false 指标。 这意味着如果没有对应特定路线或产品 ID 的页面可用,则会显示 404 错误页面。

因此我们可以使用 SSG 来预渲染许多不同类型的页面。


SSG - 关键注意事项

如前所述,SSG 为网站带来了出色的性能,因为它减少了客户端和服务器所需的处理。

这些网站也是 SEO 友好的,因为内容已经存在,并且可以由网络爬虫轻松呈现。

虽然性能和 SEO 使 SSG 成为一种很好的呈现模式,但在评估 SSG 对特定应用程序的适用性时需要考虑以下因素。

  1. 大量 HTML 文件:

需要为用户可能访问的每条可能路线生成单独的 HTML 文件。
例如,当将它用于博客时,将为数据存储中可用的每个博客文章生成一个 HTML 文件。
随后,对任何帖子的编辑都需要重新构建,以便更新反映在静态 HTML 文件中。 维护大量 HTML 文件可能具有挑战性。

  1. 托管依赖:

对于 SSG 站点来说,要超快和快速响应,用于存储和提供 HTML 文件的托管平台也应该是好的。
如果将经过良好调整的 SSG 网站托管在多个 CDN 上以利用边缘缓存,则可能获得最佳性能。

  1. 动态内容:

每次内容发生变化时,都需要构建和重新部署 SSG 站点。
如果在任何内容更改后尚未构建和部署站点,则显示的内容可能会过时。 这使得 SSG 不适合高度动态的内容。


知识点

  • getStaticProps()
  • getStaticPaths()
  • 动态路由