> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/honojs/hono/llms.txt
> Use this file to discover all available pages before exploring further.

# Static Site Generation

> Generate static HTML files from your Hono application

The SSG (Static Site Generation) helper allows you to pre-render your Hono application routes into static HTML files, enabling deployment to static hosting services.

## Import

```typescript theme={null}
import { toSSG, ssgParams, disableSSG, onlySSG } from 'hono/ssg'
```

## Basic Usage

### Generate Static Files

The `toSSG` function pre-renders your app routes:

```typescript theme={null}
import { Hono } from 'hono'
import { toSSG } from 'hono/ssg'
import fs from 'fs/promises'

const app = new Hono()

app.get('/', (c) => c.html('<h1>Home</h1>'))
app.get('/about', (c) => c.html('<h1>About</h1>'))
app.get('/contact', (c) => c.html('<h1>Contact</h1>'))

// Generate static files
const result = await toSSG(app, fs, {
  dir: './dist',
})

if (result.success) {
  console.log('Generated files:', result.files)
} else {
  console.error('Generation failed:', result.error)
}
```

This creates:

```
dist/
  index.html
  about.html
  contact.html
```

## File System Module

Provide a file system module for writing files:

### Node.js

```typescript theme={null}
import fs from 'fs/promises'
import { toSSG } from 'hono/ssg'

await toSSG(app, fs)
```

### Deno

```typescript theme={null}
import { toSSG } from 'hono/ssg'

const fs = {
  writeFile: Deno.writeFile,
  mkdir: Deno.mkdir,
}

await toSSG(app, fs)
```

### Bun

```typescript theme={null}
import { toSSG } from 'hono/ssg'

const fs = {
  writeFile: Bun.write,
  mkdir: async (path: string) => {
    await Bun.write(`${path}/.keep`, '')
  },
}

await toSSG(app, fs)
```

## Options

### Output Directory

```typescript theme={null}
await toSSG(app, fs, {
  dir: './public',
})
```

<ParamField path="dir" type="string" default="./static">
  Output directory for generated files
</ParamField>

### Concurrency

Control how many routes are processed in parallel:

```typescript theme={null}
await toSSG(app, fs, {
  concurrency: 4,
})
```

<ParamField path="concurrency" type="number" default="2">
  Number of routes to process concurrently
</ParamField>

### Extension Mapping

Customize file extensions based on content type:

```typescript theme={null}
await toSSG(app, fs, {
  extensionMap: {
    'text/html': 'html',
    'text/xml': 'xml',
    'application/xml': 'xml',
    'application/json': 'json',
  },
})
```

## Dynamic Routes

For routes with parameters, use `ssgParams` middleware to define which parameter values to generate:

### Basic Dynamic Routes

```typescript theme={null}
import { ssgParams } from 'hono/ssg'

app.get(
  '/posts/:id',
  ssgParams([{ id: '1' }, { id: '2' }, { id: '3' }]),
  (c) => {
    const id = c.req.param('id')
    return c.html(`<h1>Post ${id}</h1>`)
  }
)
```

Generates:

```
dist/
  posts/1.html
  posts/2.html
  posts/3.html
```

### Dynamic Parameters from Function

```typescript theme={null}
import { ssgParams } from 'hono/ssg'

const posts = [
  { id: '1', slug: 'hello-world' },
  { id: '2', slug: 'second-post' },
]

app.get(
  '/blog/:slug',
  ssgParams(async (c) => {
    // Fetch data from database or API
    return posts.map((post) => ({ slug: post.slug }))
  }),
  async (c) => {
    const slug = c.req.param('slug')
    const post = posts.find((p) => p.slug === slug)
    return c.html(`<h1>${post?.slug}</h1>`)
  }
)
```

### Multiple Parameters

```typescript theme={null}
import { ssgParams } from 'hono/ssg'

app.get(
  '/users/:userId/posts/:postId',
  ssgParams([
    { userId: '1', postId: 'a' },
    { userId: '1', postId: 'b' },
    { userId: '2', postId: 'c' },
  ]),
  (c) => {
    const userId = c.req.param('userId')
    const postId = c.req.param('postId')
    return c.html(`<h1>User ${userId} - Post ${postId}</h1>`)
  }
)
```

## Controlling SSG Behavior

### Disable SSG for Specific Routes

Prevent certain routes from being statically generated:

```typescript theme={null}
import { disableSSG } from 'hono/ssg'

app.get('/api/data', disableSSG(), (c) => {
  // This route will not be statically generated
  return c.json({ timestamp: Date.now() })
})
```

### Only Generate During SSG

Make routes available only during static generation:

```typescript theme={null}
import { onlySSG } from 'hono/ssg'

app.get('/preview/:id', onlySSG(), (c) => {
  // This route only exists during SSG
  // Returns 404 when running as a server
  return c.html('<h1>Preview</h1>')
})
```

### Check if in SSG Context

```typescript theme={null}
import { isSSGContext } from 'hono/ssg'

app.get('/page', (c) => {
  if (isSSGContext(c)) {
    // Running during static generation
    return c.html('<h1>Static Version</h1>')
  } else {
    // Running as a server
    return c.html('<h1>Dynamic Version</h1>')
  }
})
```

## Hooks

Customize the generation process with hooks:

### Before Request Hook

Modify requests before processing:

```typescript theme={null}
import { toSSG } from 'hono/ssg'

await toSSG(app, fs, {
  plugins: [
    {
      beforeRequestHook: async (req) => {
        // Modify request
        console.log('Processing:', req.url)
        return req
      },
    },
  ],
})
```

Return `false` to skip a route:

```typescript theme={null}
beforeRequestHook: (req) => {
  const url = new URL(req.url)
  if (url.pathname.startsWith('/admin')) {
    return false // Skip admin routes
  }
  return req
}
```

### After Response Hook

Modify responses before writing files:

```typescript theme={null}
await toSSG(app, fs, {
  plugins: [
    {
      afterResponseHook: async (res) => {
        // Modify response
        const html = await res.text()
        const modified = html.replace('{{timestamp}}', Date.now().toString())
        return new Response(modified, res)
      },
    },
  ],
})
```

Return `false` to skip writing the file:

```typescript theme={null}
afterResponseHook: (res) => {
  if (res.status !== 200) {
    return false // Skip non-200 responses
  }
  return res
}
```

### After Generate Hook

Run logic after all files are generated:

```typescript theme={null}
await toSSG(app, fs, {
  plugins: [
    {
      afterGenerateHook: async (result, fsModule, options) => {
        console.log('Generated files:', result.files.length)
        
        if (result.success) {
          // Create sitemap, copy assets, etc.
          await fsModule.writeFile(
            `${options?.dir}/sitemap.txt`,
            result.files.join('\n')
          )
        }
      },
    },
  ],
})
```

## Plugins

Plugins bundle hooks together:

```typescript theme={null}
import { toSSG, defaultPlugin, redirectPlugin } from 'hono/ssg'

await toSSG(app, fs, {
  plugins: [
    defaultPlugin(),
    redirectPlugin(),
    {
      beforeRequestHook: (req) => req,
      afterResponseHook: (res) => res,
      afterGenerateHook: async (result) => {
        console.log('Done!')
      },
    },
  ],
})
```

### Built-in Plugins

<ParamField path="defaultPlugin" type="function">
  Default plugin with standard behavior
</ParamField>

<ParamField path="redirectPlugin" type="function">
  Handles redirect responses during generation
</ParamField>

## Result Object

The `toSSG` function returns a result object:

```typescript theme={null}
interface ToSSGResult {
  success: boolean
  files: string[]
  error?: Error
}
```

### Success Case

```typescript theme={null}
const result = await toSSG(app, fs)

if (result.success) {
  console.log('Generated files:', result.files)
  // ['./static/index.html', './static/about.html', ...]
}
```

### Error Case

```typescript theme={null}
const result = await toSSG(app, fs)

if (!result.success) {
  console.error('Generation failed:', result.error?.message)
}
```

## Build Script Example

Create a build script for your project:

```typescript theme={null}
// build.ts
import { Hono } from 'hono'
import { toSSG } from 'hono/ssg'
import fs from 'fs/promises'
import app from './app'

async function build() {
  console.log('Building static site...')
  
  const result = await toSSG(app, fs, {
    dir: './dist',
    concurrency: 4,
  })
  
  if (result.success) {
    console.log(`✓ Generated ${result.files.length} files`)
    process.exit(0)
  } else {
    console.error('✗ Build failed:', result.error)
    process.exit(1)
  }
}

build()
```

Add to `package.json`:

```json theme={null}
{
  "scripts": {
    "build": "tsx build.ts"
  }
}
```

## Deployment

Deploy generated files to static hosting:

<CardGroup cols={2}>
  <Card title="Vercel" icon="triangle">
    Deploy the `dist` folder as a static site
  </Card>

  <Card title="Netlify" icon="diamond">
    Configure build command and publish directory
  </Card>

  <Card title="Cloudflare Pages" icon="cloud">
    Push to Git and configure Pages project
  </Card>

  <Card title="GitHub Pages" icon="github">
    Deploy from repository with GitHub Actions
  </Card>
</CardGroup>

## Best Practices

<Note>
  Use `ssgParams` for all dynamic routes, otherwise they will be skipped during generation.
</Note>

<Warning>
  Routes that depend on request headers or real-time data may not work correctly when statically generated.
</Warning>

<Info>
  Use `isSSGContext` to provide different behavior during static generation vs. runtime.
</Info>

## Advanced: Adaptor Interface

Create runtime-specific adaptors:

```typescript theme={null}
import { ToSSGAdaptorInterface } from 'hono/ssg'
import fs from 'fs/promises'

const toSSGAdaptor: ToSSGAdaptorInterface = (app, options) => {
  return toSSG(app, fs, options)
}

export default toSSGAdaptor
```
