SSG-Helfer
Der SSG-Helfer erzeugt aus deiner Hono-Anwendung eine statische Website. Er ruft die Inhalte der registrierten Routen ab und speichert sie als statische Dateien.
Verwendung
Manuell
Angenommen, du hast eine einfache Hono-Anwendung wie diese:
// index.tsx
const app = new Hono()
app.get('/', (c) => c.html('Hello, World!'))
app.use('/about', async (c, next) => {
c.setRenderer((content) => {
return c.html(
<html>
<head />
<body>
<p>{content}</p>
</body>
</html>
)
})
await next()
})
app.get('/about', (c) => {
return c.render(
<>
<title>Hono SSG Page</title>Hello!
</>
)
})
export default appErstelle für Node.js ein Build-Skript wie folgt:
// build.ts
import app from './index'
import { toSSG } from 'hono/ssg'
import fs from 'fs/promises'
toSSG(app, fs)Nach Ausführung des Skripts werden die Dateien wie folgt ausgegeben:
ls ./static
about.html index.htmlVite-Plugin
Mit dem Vite-Plugin @hono/vite-ssg lässt sich dieser Vorgang einfach durchführen.
Weitere Informationen findest du hier:
https://github.com/honojs/vite-plugins/tree/main/packages/ssg
toSSG
toSSG ist die Hauptfunktion zur Erzeugung statischer Websites. Sie nimmt eine Anwendung und ein Dateisystemmodul als Argumente entgegen. Ihre Grundlagen sind wie folgt:
Eingabe
Die Argumente von toSSG werden in ToSSGInterface festgelegt.
export interface ToSSGInterface {
(
app: Hono,
fsModule: FileSystemModule,
options?: ToSSGOptions
): Promise<ToSSGResult>
}appgibt eine mit registrierten Routen versehene Instanz vonnew Hono()an.fsgibt das folgende Objekt an, ausgehend vonnode:fs/promise.
export interface FileSystemModule {
writeFile(path: string, data: string | Uint8Array): Promise<void>
mkdir(
path: string,
options: { recursive: boolean }
): Promise<void | string>
}Adapter für Deno und Bun verwenden
Wenn du SSG unter Deno oder Bun verwenden möchtest, stellen die Pakete @hono/deno und @hono/bun eine Funktion toSSG bereit.
Für Deno:
import { toSSG } from '@hono/deno'
toSSG(app) // The second argument is an option typed `ToSSGOptions`.Für Bun:
import { toSSG } from '@hono/bun'
toSSG(app) // The second argument is an option typed `ToSSGOptions`.Optionen
Die Optionen werden in der Schnittstelle ToSSGOptions festgelegt.
export interface ToSSGOptions {
dir?: string
concurrency?: number
extensionMap?: Record<string, string>
plugins?: SSGPlugin[]
}dirist das Ausgabeverzeichnis für statische Dateien. Der Standardwert ist./static.concurrencygibt an, wie viele Dateien gleichzeitig erzeugt werden. Der Standardwert ist2.extensionMapist eine Zuordnung mitContent-Typeals Schlüssel und einer Zeichenfolge für die Dateiendung als Wert. Damit wird die Endung der Ausgabedatei bestimmt.pluginsist ein Array von SSG-Plugins, die die Funktionalität der statischen Website-Generierung erweitern.
Ausgabe
toSSG gibt das Ergebnis im folgenden Typ Result zurück.
export interface ToSSGResult {
success: boolean
files: string[]
error?: Error
}Dateien erzeugen
Route und Dateiname
Für die registrierten Routeninformationen und die erzeugten Dateinamen gelten die folgenden Regeln. Beim Standardverzeichnis ./static ergibt sich folgendes Verhalten:
/->./static/index.html/path->./static/path.html/path/->./static/path/index.html
Dateiendung
Die Dateiendung hängt vom Content-Type ab, den die jeweilige Route zurückgibt. Antworten von c.html werden beispielsweise als .html gespeichert.
Wenn du die Dateiendungen anpassen möchtest, setze die Option extensionMap.
import { toSSG, defaultExtensionMap } from 'hono/ssg'
// Save `application/x-html` content with `.html`
toSSG(app, fs, {
extensionMap: {
'application/x-html': 'html',
...defaultExtensionMap,
},
})Beachte, dass Pfade mit abschließendem Schrägstrich unabhängig von der Endung als index.ext gespeichert werden.
// 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'))Middleware
Im Folgenden werden integrierte Middleware-Funktionen vorgestellt, die SSG unterstützen.
ssgParams
Du kannst eine API ähnlich wie generateStaticParams von Next.js verwenden.
Beispiel:
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>
)
}
)isSSGContext
isSSGContext ist eine Helferfunktion, die true zurückgibt, wenn die aktuelle Anwendung im von toSSG ausgelösten SSG-Kontext ausgeführt wird.
app.get('/page', (c) => {
if (isSSGContext(c)) {
return c.text('This is generated by SSG')
}
return c.text('This is served dynamically')
})disableSSG
Routen mit der Middleware disableSSG werden bei der Erzeugung statischer Dateien durch toSSG ausgeschlossen.
app.get('/api', disableSSG(), (c) => c.text('an-api'))onlySSG
Routen mit der Middleware onlySSG werden nach Ausführung von toSSG durch c.notFound() überschrieben.
app.get('/static-page', onlySSG(), (c) => c.html(<h1>Welcome to my site</h1>))Plugins
Mit Plugins kannst du die statische Website-Generierung erweitern. Sie verwenden Hooks, um den Generierungsprozess in verschiedenen Phasen anzupassen.
Standard-Plugin
Standardmäßig verwendet toSSG das Plugin defaultPlugin, das Antworten mit anderen Statuscodes als 200 überspringt, etwa Weiterleitungen, Fehler oder 404. So werden für nicht erfolgreiche Antworten keine Dateien erzeugt.
import { toSSG, defaultPlugin } from 'hono/ssg'
// defaultPlugin is automatically applied when no plugins specified
toSSG(app, fs)
// Equivalent to:
toSSG(app, fs, { plugins: [defaultPlugin] })Wenn du eigene Plugins angibst, wird defaultPlugin nicht automatisch eingeschlossen. Um das Standardverhalten beizubehalten und gleichzeitig eigene Plugins hinzuzufügen, musst du es ausdrücklich angeben:
toSSG(app, fs, {
plugins: [defaultPlugin, myCustomPlugin],
})Weiterleitungs-Plugin
redirectPlugin erzeugt HTML-Weiterleitungsseiten für Routen, die HTTP-Weiterleitungsantworten (301, 302, 303, 307, 308) zurückgeben. Das erzeugte HTML enthält einen Tag <meta http-equiv="refresh"> und einen kanonischen Link.
import { toSSG, redirectPlugin, defaultPlugin } from 'hono/ssg'
toSSG(app, fs, {
plugins: [redirectPlugin(), defaultPlugin()],
})Wenn deine Anwendung beispielsweise Folgendes enthält:
app.get('/old', (c) => c.redirect('/new'))redirectPlugin erzeugt eine HTML-Datei unter /old.html mit einer Meta-Refresh-Weiterleitung zu /new.
NOTE
Wenn du es zusammen mit defaultPlugin verwendest, platziere redirectPlugin vor defaultPlugin. Da defaultPlugin Antworten mit anderen Statuscodes als 200 überspringt, würde es an erster Stelle verhindern, dass redirectPlugin Weiterleitungsantworten verarbeitet.
Hook-Typen
Plugins können die folgenden Hooks verwenden, um den Ablauf von toSSG anzupassen:
export type BeforeRequestHook = (req: Request) => Request | false
export type AfterResponseHook = (res: Response) => Response | false
export type AfterGenerateHook = (
result: ToSSGResult
) => void | Promise<void>- BeforeRequestHook: Wird vor der Verarbeitung jeder Anfrage aufgerufen. Gib
falsezurück, um die Route zu überspringen. - AfterResponseHook: Wird nach dem Empfang jeder Antwort aufgerufen. Gib
falsezurück, um die Dateierzeugung zu überspringen. - AfterGenerateHook: Wird nach Abschluss des gesamten Generierungsprozesses aufgerufen.
Plugin-Schnittstelle
export interface SSGPlugin {
beforeRequestHook?: BeforeRequestHook | BeforeRequestHook[]
afterResponseHook?: AfterResponseHook | AfterResponseHook[]
afterGenerateHook?: AfterGenerateHook | AfterGenerateHook[]
}Einfache Plugin-Beispiele
Nur GET-Anfragen berücksichtigen:
const getOnlyPlugin: SSGPlugin = {
beforeRequestHook: (req) => {
if (req.method === 'GET') {
return req
}
return false
},
}Nach Statuscode filtern:
const statusFilterPlugin: SSGPlugin = {
afterResponseHook: (res) => {
if (res.status === 200 || res.status === 500) {
return res
}
return false
},
}Erzeugte Dateien protokollieren:
const logFilesPlugin: SSGPlugin = {
afterGenerateHook: (result) => {
if (result.files) {
result.files.forEach((file) => console.log(file))
}
},
}Fortgeschrittenes Plugin-Beispiel
Hier ist ein Beispiel für ein Sitemap-Plugin, das eine Datei sitemap.xml erzeugt:
// plugins.ts
import fs from 'node:fs/promises'
import path from 'node:path'
import type { SSGPlugin } from 'hono/ssg'
import { DEFAULT_OUTPUT_DIR } from 'hono/ssg'
export const sitemapPlugin = (baseURL: string): SSGPlugin => {
return {
afterGenerateHook: (result, fsModule, options) => {
const outputDir = options?.dir ?? DEFAULT_OUTPUT_DIR
const filePath = path.join(outputDir, 'sitemap.xml')
const urls = result.files.map((file) =>
new URL(file, baseURL).toString()
)
const siteMapText = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.map((url) => `<url><loc>${url}</loc></url>`).join('\n')}
</urlset>`
fsModule.writeFile(filePath, siteMapText)
},
}
}Plugins anwenden:
import app from './index'
import { toSSG } from 'hono/ssg'
import { sitemapPlugin } from './plugins'
toSSG(app, fs, {
plugins: [
getOnlyPlugin,
statusFilterPlugin,
logFilesPlugin,
sitemapPlugin('https://example.com'),
],
})