← Cloudflare Workers / workers / static-assets / routing
Static Site Generation (SSG) и настраиваемые страницы 404
Приложения Static Site Generation (SSG) представляют собой веб-приложения, которые в основном собираются или «предварительно рендерятся» заранее. Их часто создают с помощью фреймворка, такого как Gatsby или Docusaurus. Процесс сборки этих фреймворков создаёт множество HTML-файлов и сопутствующих клиентских ресурсов (например, JavaScript-бандлы, таблицы стилей CSS, изображения, шрифты и так далее). Данные при этом либо статические и встраиваются в HTML на этапе сборки, либо запрашиваются клиентом через API с помощью клиентских запросов.
SSG-фреймворки часто позволяют создать собственную страницу 404.
Конфигурация
Чтобы развернуть приложение со статической генерацией сайта (SSG) в Workers, необходимо настроить assets.directory, и, при необходимости, assets.not_found_handling и assets.html_handling параметры в вашем конфигурационный файл 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 по умолчанию равно auto-trailing-slash и обычно это автоматически даёт нужное поведение: отдельные файлы (например, foo.html) будет отдан без конечный слеш и файлы индекса папки (например, foo/index.html) будет отдан с косую черту в конце. Также можно принудительно включить косую черту в конце (force-trailing-slash) или убирать завершающую косую черту (drop-trailing-slash) для запросов HTML-страниц.
Пользовательские страницы 404
Настройка assets.not_found_handling к 404-page переопределяет поведение обслуживания статических ресурсов Workers по умолчанию. Если входящий запрос не соответствует ни одному файлу в assets.directory, Workers будет обслуживать содержимое ближайшего 404.html файл с 404 Not Found статус.
Запросы навигации
Если у вас есть скрипт Worker (main), настроили assets.not_found_handling, и используйте assets_navigation_prefers_asset_serving флаг совместимости (или установите дату совместимости 2025-04-01 или новее), запросы навигации не вызовет скрипт Worker. запрос навигации представляет собой запрос, выполненный с помощью Sec-Fetch-Mode: navigate заголовок, который браузеры автоматически прикрепляют при переходе на страницу. Это снижает число оплачиваемых вызовов скрипта Worker и особенно полезно для приложений с большой клиентской логикой, которые иначе вызывали бы скрипт Worker слишком часто и без необходимости.
Клиентские колбэки
В некоторых случаях может потребоваться передать значение из запроса навигации в скрипт Worker. Например, если вы выступаете в роли OAuth callback, вы можете ожидать запросы к такому маршруту, как /oauth/callback?code=.... С помощью assets_navigation_prefers_asset_serving флаг, ваши HTML-ресурсы будут отдаваться напрямую, а не через скрипт Worker. В этом случае мы рекомендуем передавать значение на сервер с помощью клиентского JavaScript, либо в составе вашего клиентского приложения для соответствующего маршрута, либо через отдельный облегчённый 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>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 });
}
}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 });
}
}Локальная разработка
Если вы используете SPA-фреймворк на основе Vite, вам может быть интересно использовать Плагин Vite который предлагает разработку в нативной среде Vite.
Справочник
В большинстве случаев настройка assets.not_found_handling к 404-page обеспечит нужное поведение. Если вы создаёте собственный фреймворк или у вас есть особые требования, следующая диаграмма поможет понять, как именно принимаются решения о маршрутизации.
Полная диаграмма принятия решений маршрутизации
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Запросы тарифицируются только при вызове скрипта Worker. После этого можно обслуживать ресурсы с помощью привязки assets (на диаграмме выше показана пунктирной линией).
Подробнее о том, как происходит сопоставление ресурсов, можно прочитать в Документация по обработке HTML.