Skip to main content
doc0blade47/honodocumentation home
Sign in

Static Site Generation

Last updated Oct 12, 2026100% grounded
Relevant source files

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

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, src/helper/ssg/ssg.ts:368-372

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

Sources: src/helper/ssg/ssg.ts:205-334, src/helper/ssg/ssg.ts:368-397

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, src/helper/ssg/ssg.ts:372-397

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, src/helper/ssg/ssg.ts:223-223, src/helper/ssg/ssg.ts:309-327

Option / VariableTypeDefault / BehaviorPurpose
concurrencynumberundefined (unrestricted)Limits concurrent execution pools during route fetching.
extensionMapRecord<string, string>undefinedMaps MIME types or file extensions during output path generation.
pluginsSSGPlugin[][defaultPlugin()]Extends generation lifecycle with custom hooks.
createdDirsSet<string>New SetCaches created directory paths to optimize file writing.

Sources: src/helper/ssg/ssg.ts:195-197, src/helper/ssg/ssg.ts:309-314, src/helper/ssg/ssg.ts:372-372

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

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

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, src/helper/ssg/utils.ts:73-75

Sources: src/helper/ssg/ssg.ts:245-269, src/helper/ssg/utils.ts:73-75

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

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. 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, src/helper/ssg/middleware.ts:56-83

Helper / ConstantTypePurpose / Behavior
SSG_CONTEXTstring (HONO_SSG_CONTEXT)Environment key identifying an active static generation build.
X_HONO_DISABLE_SSG_HEADER_KEYstring (x-hono-disable-ssg)Response header key signalling that static generation is disabled for a route.
isSSGContext(c: Context) => booleanChecks if the current context environment contains the SSG tracking key.
disableSSG() => MiddlewareHandlerSkips static generation by writing the disable header and returning a 404 response.
onlySSG() => MiddlewareHandlerRestricts handler execution to static generation contexts only.

Sources: src/helper/ssg/middleware.ts:5-6, src/helper/ssg/middleware.ts:56-83

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

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

File Resolution and Output Writing

Path Calculation and Extension Mapping

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

Directory Traversal Safeguards

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

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

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, src/helper/ssg/ssg.ts:234-281

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

Client-Side Redirect HTML Synthesis

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

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 / FunctionValues / BehaviorPurpose
REDIRECT_STATUS_CODES301, 302, 303, 307, 308Identifies HTTP response statuses eligible for static redirect page generation.
generateRedirectHtmlHTML 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.
redirectPluginSSGPluginIntercepts 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

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

Was this page helpful?