跳转到内容
搜索文档

静态站点生成(SSG)与自定义 404 页面

最后更新 查看 MarkdownAgent 设置

静态站点生成(SSG)应用是主要预先构建或「预渲染」的 Web 应用。它们通常使用 GatsbyDocusaurus 等框架构建。这些框架的构建过程会生成多个 HTML 文件以及配套的客户端资源(例如 JavaScript 包、CSS 样式表、图片、字体等)。数据可能是静态的,在构建时获取并编译进 HTML;也可能由客户端通过 API 发起请求获取。

SSG 框架通常允许你创建自定义 404 页面。

配置

要将静态站点生成应用部署到 Workers,必须在 Wrangler 配置文件 中配置 assets.directory,并可选择配置 assets.not_found_handlingassets.html_handling 选项:

{
	"name": "my-worker",
	// Set this to today's date
	"compatibility_date": "2026-08-17",
	"assets": {
		"directory": "./dist/",
		"not_found_handling": "404-page",
		"html_handling": "auto-trailing-slash"
	}
}
name = "my-worker"
# Set this to today's date
compatibility_date = "2026-08-17"

[assets]
directory = "./dist/"
not_found_handling = "404-page"
html_handling = "auto-trailing-slash"

assets.html_handling 默认为 auto-trailing-slash,通常会自动提供期望的行为:单个文件(例如 foo.html)_不带_尾部斜杠提供,文件夹索引文件(例如 foo/index.html)_带_尾部斜杠提供。你也可以强制 HTML 页面使用尾部斜杠(force-trailing-slash)或去掉尾部斜杠(drop-trailing-slash)。

自定义 404 页面

assets.not_found_handling 配置为 404-page 会覆盖 Workers 静态资源的默认服务行为。当传入请求未匹配 assets.directory 中的文件时,Workers 会以 404 Not Found 状态提供最近的 404.html 文件内容。

导航请求

如果你有一个 Worker 脚本(main),已配置 assets.not_found_handling,并使用 assets_navigation_prefers_asset_serving 兼容性标志(或设置兼容性日期为 2025-04-01 或更高版本),导航请求 将不会调用 Worker 脚本。导航请求 是使用 Sec-Fetch-Mode: navigate 标头发出的请求,浏览器在导航到页面时会自动附加此标头。这减少了 Worker 脚本的可计费调用次数,对于客户端密集型应用程序特别有用,否则这些应用程序会非常频繁且不必要地调用 Worker 脚本。

客户端回调

在某些情况下,你可能需要将导航请求中的值传递给 Worker 脚本。例如,如果你充当 OAuth 回调,你可能会看到向 /oauth/callback?code=... 等路由发出的请求。使用 assets_navigation_prefers_asset_serving 标志时,将提供 HTML 资源,而不是 Worker 脚本。在这种情况下,我们建议你通过客户端 JavaScript 将值传递给服务器,可以在此适当路由的客户端应用程序中完成,或使用精简的端点特定 HTML 文件。

./dist/oauth/callback.htmlhtml
<!DOCTYPE html>
<html>
	<head>
		<title>OAuth callback</title>
	</head>
	<body>
		<p>Loading...</p>
		<script>
			(async () => {
				const response = await fetch("/api/oauth/callback" + window.location.search);
				if (response.ok) {
					window.location.href = '/';
				} else {
					document.querySelector('p').textContent = 'Error: ' + (await response.json()).error;
				}
			})();
		</script>
	</body>
</html>
./worker/index.jsjs
import { WorkerEntrypoint } from "cloudflare:workers";

export default class extends WorkerEntrypoint {
	async fetch(request) {
		const url = new URL(request.url);
		if (url.pathname === "/api/oauth/callback") {
			const code = url.searchParams.get("code");

			const sessionId =
				await exchangeAuthorizationCodeForAccessAndRefreshTokensAndPersistToDatabaseAndGetSessionId(
					code,
				);

			if (sessionId) {
				return new Response(null, {
					headers: {
						"Set-Cookie": `sessionId=${sessionId}; HttpOnly; SameSite=Strict; Secure; Path=/; Max-Age=86400`,
					},
				});
			} else {
				return Response.json(
					{ error: "Invalid OAuth code. Please try again." },
					{ status: 400 },
				);
			}
		}

		return new Response(null, { status: 404 });
	}
}
./worker/index.tsts
import { WorkerEntrypoint } from "cloudflare:workers";

export default class extends WorkerEntrypoint {
	async fetch(request: Request) {
		const url = new URL(request.url);
		if (url.pathname === "/api/oauth/callback") {
			const code = url.searchParams.get("code");

			const sessionId = await exchangeAuthorizationCodeForAccessAndRefreshTokensAndPersistToDatabaseAndGetSessionId(code);

			if (sessionId) {
				return new Response(null, {
					headers: {
						"Set-Cookie": `sessionId=${sessionId}; HttpOnly; SameSite=Strict; Secure; Path=/; Max-Age=86400`,
					},
				});
			} else {
				return Response.json(
					{ error: "Invalid OAuth code. Please try again." },
					{ status: 400 }
				);
			}
		}

		return new Response(null, { status: 404 });
	}
}

本地开发

如果你使用的是基于 Vite 的 SPA 框架,可以考虑使用我们的 Vite 插件,它提供原生 Vite 开发体验。

参考

在大多数情况下,将 assets.not_found_handling 配置为 404-page 将提供所需的行为。如果你正在构建自己的框架或有特殊需求,以下图表可以深入了解路由决策的制定方式。

完整路由决策图
flowchart
Request@{ shape: stadium, label: "传入请求" }
Request-->RunWorkerFirst
RunWorkerFirst@{ shape: diamond, label: "是否先运行 Worker 脚本?" }
RunWorkerFirst-->|请求匹配 run_worker_first 路径|WorkerScriptInvoked
RunWorkerFirst-->|请求匹配 run_worker_first 排除路径|AssetServing
RunWorkerFirst-->|无匹配|RequestMatchesAsset
RequestMatchesAsset@{ shape: diamond, label: "请求是否匹配静态资源?" }
RequestMatchesAsset-->|是|AssetServing
RequestMatchesAsset-->|否|WorkerScriptPresent
WorkerScriptPresent@{ shape: diamond, label: "是否存在 Worker 脚本?" }
WorkerScriptPresent-->|否|AssetServing
WorkerScriptPresent-->|是|RequestNavigation
RequestNavigation@{ shape: diamond, label: "是否为导航请求?" }
RequestNavigation-->|否|WorkerScriptInvoked
WorkerScriptInvoked@{ shape: rect, label: "调用 Worker 脚本" }
WorkerScriptInvoked-.->|Assets 绑定|AssetServing
RequestNavigation-->|是|AssetServing

subgraph Asset serving
	AssetServing@{ shape: diamond, label: "请求是否匹配静态资源?" }
	AssetServing-->|是|AssetServed
	AssetServed@{ shape: stadium, label: "**200 OK**<br />提供静态资源" }
	AssetServing-->|否|NotFoundHandling

	subgraph 404-page
		NotFoundHandling@{ shape: rect, label: "请求重写为 ../404.html" }
		NotFoundHandling-->404PageExists
		404PageExists@{ shape: diamond, label: "HTML 页面是否存在?" }
		404PageExists-->|是|404PageServed
		404PageExists-->|否|404PageAtIndex
		404PageAtIndex@{ shape: diamond, label: "请求是否为根路径 /404.html?" }
		404PageAtIndex-->|是|Generic404PageServed
		404PageAtIndex-->|否|NotFoundHandling
		Generic404PageServed@{ shape: stadium, label: "**404 Not Found**<br />返回空正文响应" }
		404PageServed@{ shape: stadium, label: "**404 Not Found**<br />提供 404.html 页面" }
	end

end

请求仅在调用 Worker 脚本时才计费。从那里,可以使用 assets 绑定提供资源(如上图中的虚线所示)。

你可以在 HTML 处理文档中阅读有关我们如何匹配资源的更多信息。

这篇文档对您有帮助吗?