---
title: "Static Site Generation"
description: "Static site generation (SSG) in Hono compiles application routes into static HTML assets and files through programmatic orchestrators like toSSG and generator functions such as fetchRoutesContent. ..."
last_updated: "2026-10-12T08:14:12.75099+00:00"
canonical_url: "https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/helpers-and-tooling/static-site-generation"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [src/helper/ssg/ssg.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts)
- [src/helper/route/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/route/index.ts)
- [src/helper/ssg/plugins.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/plugins.ts)
- [src/hono-base.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/hono-base.ts)
- [src/jsx/dom/render.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/jsx/dom/render.ts)
- [src/context.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/context.ts)
- [src/router/reg-exp-router/prepared-router.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/router/reg-exp-router/prepared-router.ts)
- [src/adapter/deno/ssg.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/deno/ssg.ts)
- [src/helper/ssg/utils.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/utils.ts)
- [src/adapter/cloudflare-pages/handler.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/cloudflare-pages/handler.ts)
- [src/client/client.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/client/client.ts)
- [src/adapter/aws-lambda/handler.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/aws-lambda/handler.ts)
- [src/adapter/bun/ssg.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/bun/ssg.ts)
- [src/middleware/cache/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/cache/index.ts)
- [src/helper/ssg/middleware.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/middleware.ts)
- [src/middleware/serve-static/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/serve-static/index.ts)
- [src/helper/dev/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/dev/index.ts)
- [src/helper/ssg/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/index.ts)
- [src/middleware/jsx-renderer/index.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/middleware/jsx-renderer/index.ts)
- [src/preset/quick.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/preset/quick.ts)
- [src/adapter/lambda-edge/handler.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/lambda-edge/handler.ts)
- [src/router/reg-exp-router/router.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/router/reg-exp-router/router.ts)
- [src/adapter/netlify/handler.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/netlify/handler.ts)
- [src/router/trie-router/node.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/router/trie-router/node.ts)
- [src/router/linear-router/router.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/router/linear-router/router.ts)
- [src/adapter/deno/serve-static.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/deno/serve-static.ts)
- [perf-measures/type-check/scripts/generate-app.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/perf-measures/type-check/scripts/generate-app.ts)
- [src/adapter/bun/serve-static.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/bun/serve-static.ts)
- [src/router/reg-exp-router/trie.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/router/reg-exp-router/trie.ts)
- [src/adapter/vercel/handler.ts](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/adapter/vercel/handler.ts)
</details>

## Overview

Static site generation (SSG) in Hono compiles application routes into static HTML assets and files through programmatic orchestrators like `toSSG` and generator functions such as `fetchRoutesContent`. By inspecting registered application endpoints, expanding parameter sets, and executing requests within isolated runtime contexts, Hono bridges dynamic server routing with flat-file deployment pipelines.
Sources: [src/helper/ssg/ssg.ts:205-221](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L205-L221), [src/helper/ssg/ssg.ts:368-372](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L368-L372)

## Generation Pipeline and Lifecycle

### Execution Flow and Orchestration

The static site generation lifecycle is coordinated by the public `toSSG` orchestrator and the underlying generator `fetchRoutesContent`. When invoked with a Hono application, file system module, and optional configuration parameters, `toSSG` initializes hook collections and plugin pipelines, defaulting to `defaultPlugin` when no custom plugins are supplied.
Sources: [src/helper/ssg/ssg.ts:368-372](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L368-L372)

```mermaid
flowchart TD
    A["toSSG()"] --> B["fetchRoutesContent()"]
    B --> C["filterStaticGenerateRoutes()"]
    C --> D["beforeRequestHook()"]
    D --> E["app.fetch() for ssgParams"]
    E --> F["Parameter Expansion"]
    F --> G["app.request() per param"]
    G --> H["afterResponseHook()"]
    H --> I["parseResponseContent()"]
    I --> J["saveContentToFile()"]
    J --> K["afterGenerateHook()"]
```
Sources: [src/helper/ssg/ssg.ts:205-334](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L205-L334), [src/helper/ssg/ssg.ts:368-397](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L368-L397)

> [!NOTE]
> Deprecated hook options (`beforeRequestHook`, `afterResponseHook`, `afterGenerateHook`) on `ToSSGOptions` are normalized and merged into the active plugin hook arrays alongside any configured `plugins` items during orchestrator initialization.
Sources: [src/helper/ssg/ssg.ts:185-197](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L185-L197), [src/helper/ssg/ssg.ts:372-397](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L372-L397)

### Concurrency Management and Output Writing

Execution tasks across routes and requests are managed by a pool created via `createPool` using the `concurrency` option defined in `ToSSGOptions`. As content generators yield route payloads, `saveContentToFile` tracks created directories within a module-scoped `createdDirs` set to avoid redundant directory creation calls during file writing.
Sources: [src/helper/ssg/ssg.ts:195-195](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L195-L195), [src/helper/ssg/ssg.ts:223-223](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L223-L223), [src/helper/ssg/ssg.ts:309-327](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L309-L327)

| Option / Variable | Type | Default / Behavior | Purpose |
| :--- | :--- | :--- | :--- |
| `concurrency` | `number` | `undefined` (unrestricted) | Limits concurrent execution pools during route fetching. |
| `extensionMap` | `Record<string, string>` | `undefined` | Maps MIME types or file extensions during output path generation. |
| `plugins` | `SSGPlugin[]` | `[defaultPlugin()]` | Extends generation lifecycle with custom hooks. |
| `createdDirs` | `Set<string>` | New `Set` | Caches created directory paths to optimize file writing. |

Sources: [src/helper/ssg/ssg.ts:195-197](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L195-L197), [src/helper/ssg/ssg.ts:309-314](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L309-L314), [src/helper/ssg/ssg.ts:372-372](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L372-L372)

## Route Filtering and Parameter Expansion

### Route Inspection and Filtering

The static generation pipeline begins by examining the application's registered route table via `filterStaticGenerateRoutes`. This utility reduces `hono.routes` by inspecting each route's HTTP method and handler type, retaining only endpoints capable of serving static content. Specifically, it selects routes registered with the `GET` method or `METHOD_NAME_ALL` whose target handler is not classified as middleware.
Sources: [src/helper/ssg/utils.ts:61-71](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/utils.ts#L61-L71)

> [!WARNING]
> Middleware handlers are deliberately excluded from static generation output. A route whose target handler is identified as middleware via `isMiddleware` will be filtered out, preventing middleware-only definitions from generating empty or invalid static files.
Sources: [src/helper/ssg/utils.ts:65-68](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/utils.ts#L65-L68)

### Dynamic Parameter Detection and Expansion

Once static routes are filtered, `fetchRoutesContent` evaluates each route path for dynamic segments using `isDynamicRoute`. A path is classified as dynamic if any of its slash-separated segments start with a colon (`:`) or contain a wildcard (`*`).
Sources: [src/helper/ssg/ssg.ts:225-249](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L225-L249), [src/helper/ssg/utils.ts:73-75](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/utils.ts#L73-L75)

```mermaid
flowchart TD
    A["Route Path"] --> B{"isDynamicRoute()"}
    B -->|Yes| C["Fetch route metadata & ssgParams"]
    B -->|No| D["Assign default ssgParams [{}]"]
    C --> E{"ssgParams defined?"}
    E -->|No| F["Skip / Drop Route"]
    E -->|Yes| G["Iterate ssgParams"]
    D --> G
    G --> H["replaceUrlParam()"]
    H --> I["app.request() per expansion"]
```
Sources: [src/helper/ssg/ssg.ts:245-269](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L245-L269), [src/helper/ssg/utils.ts:73-75](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/utils.ts#L73-L75)

> [!IMPORTANT]
> If a dynamic route fails to attach `ssgParams` to the request object during the initial fetch execution, the route is skipped entirely and yields no content. Non-dynamic routes automatically default to an empty parameter set `[{}]` so that a single static file is generated.
Sources: [src/helper/ssg/ssg.ts:245-251](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L245-L251)

## Static Generation Middleware Controls

### Context Tracking and Middleware Helpers

The SSG middleware module exposes runtime helpers that inspect or modify generation behavior depending on [execution context](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/core-engine/execution-context). The experimental `isSSGContext` function checks whether `c.env[SSG_CONTEXT]` evaluates to a truthy value, identifying when a request runs within a static generation build rather than a live server. Two supplementary middleware factories, `disableSSG` and `onlySSG`, leverage this environment flag to control route inclusion: `disableSSG` assigns the header `x-hono-disable-ssg` (`X_HONO_DISABLE_SSG_HEADER_KEY`) and returns a `404` via `c.notFound()`, whereas `onlySSG` restricts execution exclusively to SSG contexts, returning `404` for live requests.
Sources: [src/helper/ssg/middleware.ts:5-6](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/middleware.ts#L5-L6), [src/helper/ssg/middleware.ts:56-83](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/middleware.ts#L56-L83)

| Helper / Constant | Type | Purpose / Behavior |
| :--- | :--- | :--- |
| `SSG_CONTEXT` | `string` (`HONO_SSG_CONTEXT`) | Environment key identifying an active static generation build. |
| `X_HONO_DISABLE_SSG_HEADER_KEY` | `string` (`x-hono-disable-ssg`) | Response header key signalling that static generation is disabled for a route. |
| `isSSGContext` | `(c: Context) => boolean` | Checks if the current context environment contains the SSG tracking key. |
| `disableSSG` | `() => MiddlewareHandler` | Skips static generation by writing the disable header and returning a 404 response. |
| `onlySSG` | `() => MiddlewareHandler` | Restricts handler execution to static generation contexts only. |

Sources: [src/helper/ssg/middleware.ts:5-6](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/middleware.ts#L5-L6), [src/helper/ssg/middleware.ts:56-83](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/middleware.ts#L56-L83)

### SSG Parameter Registration

Dynamic routes register their concrete parameter combinations through the `ssgParams` middleware. This function accepts either a static `SSGParams` array or a generator function `(c: Context) => SSGParams | Promise<SSGParams>`. When encountered on a dynamic route path verified by `isDynamicRoute`, `ssgParams` evaluates the parameter definitions, attaches them to `c.req.raw.ssgParams` (cast as `AddedSSGDataRequest`), and immediately aborts subsequent handler execution by returning `c.notFound()`.
Sources: [src/helper/ssg/middleware.ts:36-49](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/middleware.ts#L36-L49)

> [!CAUTION]
> The `ssgParams` middleware invokes `c.notFound()` after attaching parameters to the raw request object. Consequently, any handler placed after `ssgParams` on the same route definition will be bypassed during the parameter-collection phase.
Sources: [src/helper/ssg/middleware.ts:43-49](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/middleware.ts#L43-L49)

## File Resolution and Output Writing

### Path Calculation and Extension Mapping

```typescript
export const saveContentToFile = async (
  data: Promise<{ routePath: string; content: string | ArrayBuffer; mimeType: string } | undefined>,
  fsModule: FileSystemModule,
  outDir: string,
  extensionMap?: Record<string, string>
): Promise<string | undefined> => {
  const awaitedData = await data
  if (!awaitedData) {
    return
  }
  const { routePath, content, mimeType } = awaitedData
  const filePath = generateFilePath(routePath, outDir, mimeType, extensionMap)
  const dirPath = dirname(filePath)

  if (!createdDirs.has(dirPath)) {
    await fsModule.mkdir(dirPath, { recursive: true })
    createdDirs.add(dirPath)
  }
  if (typeof content === 'string') {
    await fsModule.writeFile(filePath, content)
  } else if (content instanceof ArrayBuffer) {
    await fsModule.writeFile(filePath, new Uint8Array(content))
  }
  return filePath
}
```
Sources: [src/helper/ssg/ssg.ts:310-334](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L310-L334)

### Directory Traversal Safeguards

```typescript
export const ensureWithinOutDir = (outDir: string, filePath: string): void => {
  const normalizedOutDir = joinPaths('/', outDir)
  const normalizedFilePath = joinPaths('/', filePath)

  if (
    normalizedFilePath !== normalizedOutDir &&
    !normalizedFilePath.startsWith(`${normalizedOutDir}/`)
  ) {
    throw new Error(`Path traversal detected: "${filePath}" is outside the output directory`)
  }
}
```
Sources: [src/helper/ssg/utils.ts:77-87](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/utils.ts#L77-L87)

> [!CAUTION]
> The `ensureWithinOutDir` helper normalizes both the target output directory and the calculated file path via `joinPaths`, explicitly verifying that the file path matches or strictly begins with the normalized output directory prefix followed by a slash. If a route path attempts directory traversal using relative segments, an error is thrown to halt output writing.
Sources: [src/helper/ssg/utils.ts:77-87](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/utils.ts#L77-L87)

## Lifecycle Hooks and Redirect Plugins

### Hook Interception Points

The static site generation pipeline exposes multiple hook interception points across route execution to inspect or transform requests and responses. Before a route request is evaluated, `beforeRequestHook` intercepts the incoming `Request` object (`AddedSSGDataRequest`), allowing modifications or cancellation by returning a falsy value. Similarly, `afterResponseHook` evaluates generated `Response` instances. If a response carries the header defined by `X_HONO_DISABLE_SSG_HEADER_KEY`, generation for that route is skipped.
Sources: [src/helper/ssg/ssg.ts:185-194](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L185-L194), [src/helper/ssg/ssg.ts:234-281](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/ssg.ts#L234-L281)

> [!WARNING]
> The `defaultPlugin` checks response status codes and rejects any response where `res.status !== 200`, meaning non-success responses will be dropped unless handled by custom plugins placed earlier in the plugin chain.
Sources: [src/helper/ssg/plugins.ts:11-20](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/plugins.ts#L11-L20)

### Client-Side Redirect HTML Synthesis

```typescript
const REDIRECT_STATUS_CODES = new Set([301, 302, 303, 307, 308])

const generateRedirectHtml = (location: string) => {
  // prettier-ignore
  const content = html`<!DOCTYPE html>
<title>Redirecting to: ${location}</title>
<meta http-equiv="refresh" content="0;url=${location}" />
<meta name="robots" content="noindex" />
<link rel="canonical" href="${location}" />
<body>
<a href="${location}">Redirecting to <code>${location}</code></a>
</body>
`
  return content.toString().replace(/\n/g, '')
}
```
Sources: [src/helper/ssg/plugins.ts:22-36](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/plugins.ts#L22-L36)

The `redirectPlugin` catches specific HTTP redirect status codes and transforms them into self-contained HTML redirection pages suitable for static hosting environments where server-side headers are absent.

| Constant Set / Function | Values / Behavior | Purpose |
| :--- | :--- | :--- |
| `REDIRECT_STATUS_CODES` | `301`, `302`, `303`, `307`, `308` | Identifies HTTP response statuses eligible for static redirect page generation. |
| `generateRedirectHtml` | HTML string with `<meta http-equiv="refresh">` | Synthesizes a static HTML document containing client-side refresh tags and fallback anchor links pointing to the destination `Location` header. |
| `redirectPlugin` | `SSGPlugin` | Intercepts redirect responses, extracts the `Location` header, and returns a `200` HTML `Response` wrapping the synthesized redirect markup. |

Sources: [src/helper/ssg/plugins.ts:22-72](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/plugins.ts#L22-L72)

> [!TIP]
> When using `redirectPlugin` alongside `defaultPlugin`, position `redirectPlugin` first in the plugins array. Because `defaultPlugin` rejects non-200 responses, placing `defaultPlugin` before `redirectPlugin` would drop redirect responses before they can be converted into HTML pages.
Sources: [src/helper/ssg/plugins.ts:38-49](https://github.com/blade47/hono/blob/e50df01453e71b071c3e6136b161b160b9fdf916/src/helper/ssg/plugins.ts#L38-L49)

## Related

- [Static File Serving](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/helpers-and-tooling/static-file-serving)
- [JSX Runtime](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/technical/jsx-and-ui/jsx-runtime)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/552ca36e-f67e-41c3-a07a-def9bd9551b0/llms.txt) for all pages in this wiki.
