INTEGRITY Dokumentace

Generování statických stránek (SSG) a vlastní stránky 404

Aplikace s generováním statických stránek (SSG) jsou webové aplikace, které jsou z větší části sestaveny, neboli „přerenderovány“, předem. Často se vytvářejí pomocí frameworku, jako je Gatsby nebo Docusaurus. Build proces těchto frameworků vygeneruje řadu HTML souborů a doprovodných zdrojů na straně klienta (například JavaScript bundly, CSS styly, obrázky, fonty a podobně). Data jsou buď statická a zkompilovaná do HTML již při buildu, nebo si je klient načítá z API pomocí požadavků na straně klienta.

SSG framework vám často umožní vytvořit vlastní stránku 404.

Konfigurace

Chcete-li nasadit aplikaci typu Static Site Generation do Workers, musíte nakonfigurovat assets.directory, a volitelně assets.not_found_handling a assets.html_handling možnosti ve svém Konfigurační soubor Wrangler:

{
	"name": "my-worker",
	// Set this to today's date
	"compatibility_date": "2026-08-28",
	"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-28"

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

assets.html_handling má výchozí hodnotu auto-trailing-slash a to obvykle automaticky zajistí požadované chování: jednotlivé soubory (např. foo.html) se obslouží bez koncové lomítko a indexové soubory složek (např. foo/index.html) se obslouží s koncové lomítko. Případně můžete vynutit koncová lomítka (force-trailing-slash) nebo odstraňte koncová lomítka (drop-trailing-slash) u požadavků na HTML stránky.

Vlastní stránky 404

Konfigurace assets.not_found_handling na 404-page přepisuje výchozí chování obsluhy Workers pro statické soubory. Pokud příchozí požadavek neodpovídá žádnému souboru ve assets.directory, Workers odešle obsah nejbližšího 404.html soubor s 404 Not Found stav.

Pokud máte skript Workeru (main), mají nakonfigurováno assets.not_found_handling, a použít assets_navigation_prefers_asset_serving příznak kompatibility (nebo nastavte datum kompatibility na 2025-04-01 nebo novější), navigační požadavky nevyvolá skript Workeru. navigační požadavek je požadavek vytvořený pomocí Sec-Fetch-Mode: navigate hlavičku, kterou prohlížeče automaticky připojují při přechodu na stránku. Díky tomu se sníží počet zpoplatněných vyvolání vašeho skriptu Workeru, což se hodí zejména u aplikací náročných na klientský kód, které by jinak Worker skript vyvolávaly velmi často a zbytečně.

Callbacky na straně klienta

V některých případech může být potřeba předat hodnotu z navigačního požadavku do vašeho Worker skriptu. Pokud například fungujete jako OAuth callback, můžete očekávat požadavky na určitou trasu, například /oauth/callback?code=.... S assets_navigation_prefers_asset_serving příznak budou vaše HTML podklady obsluhovány staticky namísto vašeho Worker skriptu. V takovém případě doporučujeme, ať už jako součást klientské aplikace pro danou trasu, nebo pomocí zjednodušeného HTML souboru určeného pro konkrétní endpoint, předat hodnotu na server pomocí klientského JavaScriptu.

./dist/oauth/callback.html
<!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.js
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.ts
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 });
	}
}

Lokální vývoj

Pokud používáte SPA framework postavený na Vite, může vás zajímat Plugin Vite který nabízí vývojářský zážitek nativní pro Vite.

Referenční informace

Ve většině případů stačí nakonfigurovat assets.not_found_handling na 404-page poskytne požadované chování. Pokud si vytváříte vlastní framework nebo máte specifické požadavky, následující diagram vám ukáže, jak přesně se rozhodování o směrování provádí.

Kompletní diagram rozhodování o směrování
flowchart
Request@{ shape: stadium, label: "Incoming request" }
Request-->RunWorkerFirst
RunWorkerFirst@{ shape: diamond, label: "Run Worker script first?" }
RunWorkerFirst-->|Request matches run_worker_first path|WorkerScriptInvoked
RunWorkerFirst-->|Request matches run_worker_first negative path|AssetServing
RunWorkerFirst-->|No matches|RequestMatchesAsset
RequestMatchesAsset@{ shape: diamond, label: "Request matches asset?" }
RequestMatchesAsset-->|Yes|AssetServing
RequestMatchesAsset-->|No|WorkerScriptPresent
WorkerScriptPresent@{ shape: diamond, label: "Worker script present?" }
WorkerScriptPresent-->|No|AssetServing
WorkerScriptPresent-->|Yes|RequestNavigation
RequestNavigation@{ shape: diamond, label: "Request is navigation request?" }
RequestNavigation-->|No|WorkerScriptInvoked
WorkerScriptInvoked@{ shape: rect, label: "Worker script invoked" }
WorkerScriptInvoked-.->|Asset binding|AssetServing
RequestNavigation-->|Yes|AssetServing

subgraph Asset serving
	AssetServing@{ shape: diamond, label: "Request matches asset?" }
	AssetServing-->|Yes|AssetServed
	AssetServed@{ shape: stadium, label: "**200 OK**<br />asset served" }
	AssetServing-->|No|NotFoundHandling

	subgraph 404-page
		NotFoundHandling@{ shape: rect, label: "Request rewritten to ../404.html" }
		NotFoundHandling-->404PageExists
		404PageExists@{ shape: diamond, label: "HTML Page exists?" }
		404PageExists-->|Yes|404PageServed
		404PageExists-->|No|404PageAtIndex
		404PageAtIndex@{ shape: diamond, label: "Request is for root /404.html?" }
		404PageAtIndex-->|Yes|Generic404PageServed
		404PageAtIndex-->|No|NotFoundHandling
		Generic404PageServed@{ shape: stadium, label: "**404 Not Found**<br />null-body response served" }
		404PageServed@{ shape: stadium, label: "**404 Not Found**<br />404.html page served" }
	end

end

Požadavky se účtují pouze v případě, že je vyvolán skript Workeru. Odtud je pak možné obsluhovat aktiva pomocí vazby assets (na diagramu výše znázorněné čárkovanou čarou).

Více o tom, jak assets přiřazujeme, se dočtete v Dokumentace ke zpracování HTML.