コンテンツにスキップ

SSG ヘルパー

SSG ヘルパーは、Hono アプリケーションから静的サイトを生成します。登録されたルートの内容を取得し、静的ファイルとして保存します。

使い方

手動

以下のような単純な Hono アプリケーションがある場合

tsx
// index.tsx
const app = new Hono()

app.get('/', (c) => c.html('Hello, World!'))
app.use('/about', async (c, next) => {
  c.setRenderer((content, head) => {
    return c.html(
      <html>
        <head>
          <title>{head.title ?? ''}</title>
        </head>
        <body>
          <p>{content}</p>
        </body>
      </html>
    )
  })
  await next()
})
app.get('/about', (c) => {
  return c.render('Hello!', { title: 'Hono SSG Page' })
})

export default app

Node.js の場合は、以下のようなビルドスクリプトを作成します

ts
// build.ts
import app from './index'
import { toSSG } from 'hono/ssg'
import fs from 'fs/promises'

toSSG(app, fs)

スクリプトを実行すると、ファイルは以下のように出力されます

bash
ls ./static
about.html  index.html

Vite プラグイン

@hono/vite-ssg Vite プラグインを使用すると、プロセスを簡単に処理できます。

詳細は、こちらをご覧ください

https://github.com/honojs/vite-plugins/tree/main/packages/ssg

toSSG

toSSG は静的サイトを生成するためのメイン関数で、アプリケーションとファイルシステムモジュールを引数に取ります。以下に基づいています

入力

toSSG の引数は ToSSGInterface で指定されます。

ts
export interface ToSSGInterface {
  (
    app: Hono,
    fsModule: FileSystemModule,
    options?: ToSSGOptions
  ): Promise<ToSSGResult>
}
  • app は、登録されたルートを持つ new Hono() を指定します。
  • fs は、node:fs/promise を想定した以下のオブジェクトを指定します。
ts
export interface FileSystemModule {
  writeFile(path: string, data: string | Uint8Array): Promise<void>
  mkdir(
    path: string,
    options: { recursive: boolean }
  ): Promise<void | string>
}

Deno と Bun 用のアダプターの使用

Deno または Bun で SSG を使用する場合、各ファイルシステムに toSSG 関数が提供されています。

Deno の場合

ts
import { toSSG } from 'hono/deno'

toSSG(app) // The second argument is an option typed `ToSSGOptions`.

Bun の場合

ts
import { toSSG } from 'hono/bun'

toSSG(app) // The second argument is an option typed `ToSSGOptions`.

オプション

オプションは ToSSGOptions インターフェースで指定されます。

ts
export interface ToSSGOptions {
  dir?: string
  concurrency?: number
  beforeRequestHook?: BeforeRequestHook
  afterResponseHook?: AfterResponseHook
  afterGenerateHook?: AfterGenerateHook
  extensionMap?: Record<string, string>
}
  • dir は静的ファイルの出力先です。デフォルト値は ./static です。
  • concurrency は、同時に生成されるファイルの並列数です。デフォルト値は 2 です。
  • extensionMap は、Content-Type をキーとし、拡張子の文字列を値とするマップです。出力ファイルのファイル拡張子を決定するために使用されます。

各フックについては後述します。

出力

toSSG は、Result タイプで結果を返します。

ts
export interface ToSSGResult {
  success: boolean
  files: string[]
  error?: Error
}

フック

オプションで以下のカスタムフックを指定することで、toSSG のプロセスをカスタマイズできます。

ts
export type BeforeRequestHook = (req: Request) => Request | false
export type AfterResponseHook = (res: Response) => Response | false
export type AfterGenerateHook = (
  result: ToSSGResult
) => void | Promise<void>

BeforeRequestHook/AfterResponseHook

toSSG は app に登録されているすべてのルートを対象としますが、除外したいルートがある場合は、フックを指定することでフィルタリングできます。

たとえば、GET リクエストのみを出力したい場合は、beforeRequestHookreq.method をフィルタリングします。

ts
toSSG(app, fs, {
  beforeRequestHook: (req) => {
    if (req.method === 'GET') {
      return req
    }
    return false
  },
})

たとえば、StatusCode が 200 または 500 の場合のみ出力したい場合は、afterResponseHookres.status をフィルタリングします。

ts
toSSG(app, fs, {
  afterResponseHook: (res) => {
    if (res.status === 200 || res.status === 500) {
      return res
    }
    return false
  },
})

AfterGenerateHook

toSSG の結果をフックしたい場合は、afterGenerateHook を使用します。

ts
toSSG(app, fs, {
  afterGenerateHook: (result) => {
    if (result.files) {
      result.files.forEach((file) => console.log(file))
    }
  })
})

ファイルの生成

ルートとファイル名

登録されたルート情報と生成されるファイル名には、以下のルールが適用されます。デフォルトの ./static は次のように動作します

  • / -> ./static/index.html
  • /path -> ./static/path.html
  • /path/ -> ./static/path/index.html

ファイル拡張子

ファイル拡張子は、各ルートから返される Content-Type に依存します。たとえば、c.html からのレスポンスは .html として保存されます。

ファイル拡張子をカスタマイズする場合は、extensionMap オプションを設定します。

ts
import { toSSG, defaultExtensionMap } from 'hono/ssg'

// Save `application/x-html` content with `.html`
toSSG(app, fs, {
  extensionMap: {
    'application/x-html': 'html',
    ...defaultExtensionMap,
  },
})

スラッシュで終わるパスは、拡張子に関係なく index.ext として保存されることに注意してください。

ts
// save to ./static/html/index.html
app.get('/html/', (c) => c.html('html'))

// save to ./static/text/index.txt
app.get('/text/', (c) => c.text('text'))

ミドルウェア

SSG をサポートする組み込みミドルウェアを紹介します。

ssgParams

Next.js の generateStaticParams のような API を使用できます。

ts
app.get(
  '/shops/:id',
  ssgParams(async () => {
    const shops = await getShops()
    return shops.map((shop) => ({ id: shop.id }))
  }),
  async (c) => {
    const shop = await getShop(c.req.param('id'))
    if (!shop) {
      return c.notFound()
    }
    return c.render(
      <div>
        <h1>{shop.name}</h1>
      </div>
    )
  }
)

disableSSG

disableSSG ミドルウェアが設定されたルートは、toSSG による静的ファイル生成から除外されます。

ts
app.get('/api', disableSSG(), (c) => c.text('an-api'))

onlySSG

onlySSG ミドルウェアが設定されたルートは、toSSG 実行後に c.notFound() によってオーバーライドされます。

ts
app.get('/static-page', onlySSG(), (c) => c.html(<h1>Welcome to my site</h1>))

MIT ライセンスでリリースされています。