静态站点生成(SSG)概述

静态站点生成,通常称为“SSG”,是将网站页面预渲染为静态 HTML 文件的过程。其好处在于,当访问者请求网页时,响应是一个预生成的 HTML 文件(静态文件),不需要在访问者的浏览器上“重新构建”网页的 HTML,也不需要由服务器动态创建(稍后会详细介绍)。

此外,由于 Qwik 的底层架构,静态站点生成还可以通过不需要 JavaScript 的“hydration”步骤来提高页面性能,这可以显著降低性能并减慢用户的交互速度。通过使用 SSG 预渲染静态的 index.html 文件,并结合 Qwik 的可恢复性,静态站点生成相对于传统解决方案提供了许多性能优势。

SSG vs. 服务器端渲染(SSR)

Qwik City 能够接受 Qwik 应用程序(无论是“webapp”还是“website”),并生成静态 HTML。一旦生成为 HTML,Qwik 就能够通过使用可恢复性跳过重新构建应用程序,因为应用程序已经生成为 HTML。静态站点生成(SSG)和服务器端渲染(SSR)使用相同的过程来生成 HTML。然而,SSG 和 SSR 之间的主要区别在于 HTML 的生成“时间”。

在传统的设置中,SSG 在构建时预渲染每个网页,而 SSR 在每个 HTTP 请求时渲染每个网页。SSG 只需要每次构建时生成一次 HTML,这对于多个访问者应该看到相同内容的网页非常有用。相比之下,SSR 对于每个访问者可能不同的网页非常有用,并且需要为每个单独的 HTTP 请求渲染自定义的 HTML。

例如,SSG 对于博客或文档站点非常理想,因为所有内容对于多个访问者应该是相同的。虽然 SSR 对于博客可能也可以正常工作,但对于 HTTP 服务器来说,为每个访问者渲染博客内容可能是一种不必要的负担,尽管他们最终都会看到相同的 HTML。

然而,账户仪表板通常对于每个已登录用户都有不同的内容。在这种设置中,每个用户都应该获得自己的渲染 HTML,其中包含他们的账户信息,而不是每个人都看到完全相同的内容。这就是 SSR 更受欢迎的地方。

理想情况下,您能够尽可能多地使用静态站点生成,因为这将减少服务器成本并提高响应时间。

然而,在 Qwik City 中,使用 SSG 还是 SSR 的决策不必是二选一的决策。相反,您自己的实现可以选择让某些路由路径使用 SSG,而其他页面使用 SSR。这完全取决于您和您的需求。

静态站点生成配置

静态站点生成是通过内置的适配器创建的,要创建适配器,请运行:

npm run qwik add static

选择 Adapter: Static site (.html files)。完成!

更改

运行上述命令将自动对您的项目进行以下更改:

  • 一个 build.server 脚本将自动添加到您的 package.json 文件中。
  • 将创建一个 adapters/static/vite.config.ts 文件。

您的构建文件将生成到 dist 文件夹中。

您可以使用以下命令构建静态站点:

npm run build

SSG 配置

adapters/static/vite.config.ts 文件还包括 SSG 配置,每个实现都可以自定义。

origin

URL 的 origin,它是方案(协议)和主机名(域名)的组合。例如,https://qwik.builder.io 的协议是 https://,域名是 qwik.builder.io。然而,origin 不包括 pathname

在静态站点生成(SSG)期间,origin 用于提供完整的 URL,并模拟完整的 URL 而不仅仅是 pathname。例如,为了渲染正确的 canonical 标签 URL 或 sitemap.xml 中的 URL,必须提供 origin

如果站点还以 / 之外的路径开始,请在 Vite 配置选项中使用 base 选项(Qwik City 配置选项中的 basePathname 选项已弃用)。

outDir

outDir 是一个文件系统输出目录,用于写入静态文件。在上面的示例中,它使用 Node 的 fileURLToPath 来创建一个绝对的文件系统路径,以便将静态 HTML 文件写入其中。

JavaScript 运行时

对于 JavaScript 项目来说,构建的运行时通常是构建在 Node.js 之上的。然而,Qwik City 静态站点生成的核心并不仅限于使用 Node.js,这就是为什么从 @builder.io/qwik-city/static/node 导入 qwikCityGenerate() 函数的原因。通过将生成函数限定在特定的运行时(例如 Node.js)范围内,Qwik City 也可以在将来从其他运行时(如 DenoBun)生成 SSG。

动态 SSG 路由

到目前为止,我们只讨论了如何为单个路由路径生成静态 HTML 文件。然而,在大多数情况下,您可能希望为具有动态参数的多个路由路径生成 HTML 文件。例如,产品站点可能对每个产品都有一个路由路径,例如 /product/:id。在这种情况下,您将希望为每个产品页面生成 HTML 文件,这将需要为每个产品 ID 生成 HTML 文件。

import { component$ } from '@builder.io/qwik';
import { useLocation, type StaticGenerateHandler } from '@builder.io/qwik-city';
import { loadProductIds } from './load-product-ids';
 
export default component$(() => {
  const { params } = useLocation();
 
  return <p>示例:{params.id}</p>;
});
 
export const onStaticGenerate: StaticGenerateHandler = async ({ env }) => {
  // 加载此用例的参数示例
  // 每个实现都会有所不同
  const ids = await loadProductIds({
    apiKey: env.get('API_KEY'),
  });
 
  return {
    params: ids.map((id) => {
      return { id };
    }),
  };
};

在上面的示例中,onStaticGenerate() 函数通过从环境变量中检索的 API 密钥请求 API 来自 loadProductIds() 函数加载产品 ID。这个函数对于每个实现来说都是自定义的,但是一般的想法是您需要为每个产品 ID 加载数据,然后为每个产品 ID 生成 HTML 文件。

onStaticGenerate 函数应该从模块的顶层导出,并且应该返回一个带有 params 属性的对象。params 属性应该是一个对象数组,其中每个对象是路由路径的一组参数。例如,如果路由路径是 /product/:id,那么 params 数组应该是一个带有 id 属性的对象数组。

此示例的目录结构如下:

src/
└── routes/
    └── product/
        └── [id]/
            └── index.tsx

请注意,index.tsx 文件位于名为 [id] 的目录中。这是一个特殊的目录名称,告诉 Qwik City 为每个 id 参数生成 HTML 文件。

Contributors

Thanks to all the contributors who have helped make this documentation better!

  • adamdbradley
  • AnthonyPAlicea
  • mhevery
  • hamatoyogi