渲染模式
静态渲染
基于我们对 SSR 的讨论,我们知道服务器上的高请求处理时间会对 TTFB 产生负面影响。类似地,对于 CSR,由于下载和处理脚本所需的时间,大型 JavaScript 包可能对应用程序的 FCP、LCP 和 TTI 有害。
静态渲染或静态生成 (SSG) 试图通过向客户端提供在网站构建时生成的预渲染 HTML 内容来解决这些问题。
每个用户可以访问的路由都会提前生成一个静态 HTML 文件。这些静态 HTML 文件可能存在于服务器或 CDN 上,并在客户端请求时获取。
静态文件也可以被缓存,从而提供更大的弹性。由于 HTML 响应是预先生成的,因此服务器上的处理时间可以忽略不计,从而导致更快的 TTFB 和更好的性能。在理想情况下,客户端 JS 应该最小化,并且静态页面应该在客户端收到响应后不久变得交互式。因此,SSG 有助于实现更快的 FCP/TTI。
基本结构
顾名思义,静态渲染非常适合静态内容,这些内容不需要根据登录用户进行自定义(例如个性化推荐)。因此,网站的“关于我们”、“联系我们”、“博客”页面或电子商务应用程序的产品页面非常适合静态渲染。Next.js、Gatsby 和 VuePress 等框架支持静态生成。让我们从这个简单的 Next.js 示例 开始,该示例展示了如何在没有数据的情况下进行静态内容渲染。
Next.js
// pages/about.js
export default function About() {
return (
<div>
<h1>About Us</h1>
{/* ... */}
</div>
);
}
当网站构建时(使用 `next build`),此页面将被预渲染为一个 HTML 文件 `about.html`,可以在路由 `/about` 上访问。
带有数据的 SSG
像“关于我们”或“联系我们”页面中的静态内容可以直接渲染,而无需从数据存储中获取数据。但是,对于像单个博客页面或产品页面这样的内容,必须将来自数据存储的数据与特定模板合并,然后在构建时渲染为 HTML。
生成的 HTML 页面数量将取决于博客文章的数量或产品的数量。要访问这些页面,您可能还需要列表页面,这些页面将是包含分类和格式化数据项列表的 HTML 页面。这些场景可以使用 Next.js 静态渲染来解决。我们可以根据可用项目生成列表页面或单个项目页面。让我们看看如何操作。
列表页面 - 所有项目
生成列表页面是一种场景,其中页面上要显示的内容取决于外部数据。此数据将在构建时从数据库中获取以构建页面。在 Next.js 中,这可以通过在页面组件中导出函数 `getStaticProps()` 来实现。该函数在构建服务器上的构建时被调用以获取数据。然后,可以将数据传递给页面的 `props` 以预渲染页面组件。让我们看看生成产品列表页面的代码,该代码最初是作为 这篇文章的一部分共享的。
// This function runs at build time on the build server
export async function getStaticProps() {
return {
props: {
products: await getProductsFromDatabase(),
},
};
}
// The page component receives products prop from getStaticProps at build time
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()`。该函数将返回所有可能的 product id,这些 id 可用于在构建时预渲染单个产品页面。以下 Next.js 骨架 此处 展示了如何构建此代码。
// pages/products/[id].js
// In getStaticPaths(), you need to return the list of
// ids of product pages (/products/[id]) that you'd
// like to pre-render at build time. To do so,
// you can fetch all products from a database.
export async function getStaticPaths() {
const products = await getProductsFromDatabase();
const paths = products.map((product) => ({
params: { id: product.id },
}));
// fallback: false means pages that don't have the correct id will 404.
return { paths, fallback: false };
}
// params will contain the id for each generated page.
export async function getStaticProps({ params }) {
return {
props: {
product: await getProductFromDatabase(params.id),
},
};
}
export default function Product({ product }) {
// Render product
}
产品页面上的详细信息可以通过使用特定 product id 的函数 `getStaticProps` 在构建时填充。请注意此处使用了 fallback: false 指标。这意味着如果特定路由或 product Id 没有相应的页面,则将显示 404 错误页面。
因此,我们可以使用 SSG 预渲染许多不同类型的页面。
SSG - 主要注意事项
正如所讨论的,SSG 可以极大地提高网站的性能,因为它减少了客户端和服务器上所需的处理。网站也对 SEO 友好,因为内容已经存在,并且无需额外努力即可被网络爬虫渲染。虽然性能和 SEO 使 SSG 成为一种很棒的渲染模式,但在评估 SSG 是否适合特定应用程序时,需要考虑以下因素。
-
大量 HTML 文件: 每个用户可能访问的路由都需要生成单独的 HTML 文件。例如,当将其用于博客时,将为数据存储中可用的每篇博文生成一个 HTML 文件。随后,对任何文章的编辑都需要重新构建,才能使更新反映在静态 HTML 文件中。维护大量 HTML 文件可能很具有挑战性。
-
托管依赖性: 为了使 SSG 网站超级快速并快速响应,用于存储和服务 HTML 文件的托管平台也应该很好。如果将性能优异的 SSG 网站直接托管在多个 CDN 上以利用边缘缓存,则可以实现卓越的性能。
-
动态内容: 每次内容发生变化时,都需要构建和重新部署 SSG 网站。如果网站在内容发生任何更改后未构建 + 部署,则显示的内容可能已过期。这使得 SSG 不适合高度动态的内容。