SSG ヘルパー
SSG ヘルパーは、Hono アプリケーションから静的サイトを生成します。登録されたルートの内容を取得し、静的ファイルとして保存します。
使い方
手動
以下のような単純な Hono アプリケーションがある場合
// 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 の場合は、以下のようなビルドスクリプトを作成します
// build.ts
import app from './index'
import { toSSG } from 'hono/ssg'
import fs from 'fs/promises'
toSSG(app, fs)
スクリプトを実行すると、ファイルは以下のように出力されます
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 で指定されます。
export interface ToSSGInterface {
(
app: Hono,
fsModule: FileSystemModule,
options?: ToSSGOptions
): Promise<ToSSGResult>
}
app
は、登録されたルートを持つnew Hono()
を指定します。fs
は、node:fs/promise
を想定した以下のオブジェクトを指定します。
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 の場合
import { toSSG } from 'hono/deno'
toSSG(app) // The second argument is an option typed `ToSSGOptions`.
Bun の場合
import { toSSG } from 'hono/bun'
toSSG(app) // The second argument is an option typed `ToSSGOptions`.
オプション
オプションは ToSSGOptions インターフェースで指定されます。
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 タイプで結果を返します。
export interface ToSSGResult {
success: boolean
files: string[]
error?: Error
}
フック
オプションで以下のカスタムフックを指定することで、toSSG
のプロセスをカスタマイズできます。
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 リクエストのみを出力したい場合は、beforeRequestHook
で req.method
をフィルタリングします。
toSSG(app, fs, {
beforeRequestHook: (req) => {
if (req.method === 'GET') {
return req
}
return false
},
})
たとえば、StatusCode が 200 または 500 の場合のみ出力したい場合は、afterResponseHook
で res.status
をフィルタリングします。
toSSG(app, fs, {
afterResponseHook: (res) => {
if (res.status === 200 || res.status === 500) {
return res
}
return false
},
})
AfterGenerateHook
toSSG
の結果をフックしたい場合は、afterGenerateHook
を使用します。
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
オプションを設定します。
import { toSSG, defaultExtensionMap } from 'hono/ssg'
// Save `application/x-html` content with `.html`
toSSG(app, fs, {
extensionMap: {
'application/x-html': 'html',
...defaultExtensionMap,
},
})
スラッシュで終わるパスは、拡張子に関係なく index.ext として保存されることに注意してください。
// 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 を使用できます。
例
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
による静的ファイル生成から除外されます。
app.get('/api', disableSSG(), (c) => c.text('an-api'))
onlySSG
onlySSG
ミドルウェアが設定されたルートは、toSSG
実行後に c.notFound()
によってオーバーライドされます。
app.get('/static-page', onlySSG(), (c) => c.html(<h1>Welcome to my site</h1>))