Zum Inhalt springen

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:

tsx
// 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 app

Erstelle für Node.js ein Build-Skript wie folgt:

ts
// 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:

bash
ls ./static
about.html  index.html

Vite-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.

ts
export interface ToSSGInterface {
  (
    app: Hono,
    fsModule: FileSystemModule,
    options?: ToSSGOptions
  ): Promise<ToSSGResult>
}
  • app gibt eine mit registrierten Routen versehene Instanz von new Hono() an.
  • fs gibt das folgende Objekt an, ausgehend von node:fs/promise.
ts
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:

ts
import { toSSG } from '@hono/deno'

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

Für Bun:

ts
import { toSSG } from '@hono/bun'

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

Optionen ​

Die Optionen werden in der Schnittstelle ToSSGOptions festgelegt.

ts
export interface ToSSGOptions {
  dir?: string
  concurrency?: number
  extensionMap?: Record<string, string>
  plugins?: SSGPlugin[]
}
  • dir ist das Ausgabeverzeichnis für statische Dateien. Der Standardwert ist ./static.
  • concurrency gibt an, wie viele Dateien gleichzeitig erzeugt werden. Der Standardwert ist 2.
  • extensionMap ist eine Zuordnung mit Content-Type als Schlüssel und einer Zeichenfolge für die Dateiendung als Wert. Damit wird die Endung der Ausgabedatei bestimmt.
  • plugins ist 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.

ts
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.

ts
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.

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'))

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:

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>
    )
  }
)

isSSGContext ​

isSSGContext ist eine Helferfunktion, die true zurückgibt, wenn die aktuelle Anwendung im von toSSG ausgelösten SSG-Kontext ausgeführt wird.

ts
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.

ts
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.

ts
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.

ts
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:

ts
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.

ts
import { toSSG, redirectPlugin, defaultPlugin } from 'hono/ssg'

toSSG(app, fs, {
  plugins: [redirectPlugin(), defaultPlugin()],
})

Wenn deine Anwendung beispielsweise Folgendes enthält:

ts
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:

ts
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 false zurück, um die Route zu überspringen.
  • AfterResponseHook: Wird nach dem Empfang jeder Antwort aufgerufen. Gib false zurück, um die Dateierzeugung zu überspringen.
  • AfterGenerateHook: Wird nach Abschluss des gesamten Generierungsprozesses aufgerufen.

Plugin-Schnittstelle ​

ts
export interface SSGPlugin {
  beforeRequestHook?: BeforeRequestHook | BeforeRequestHook[]
  afterResponseHook?: AfterResponseHook | AfterResponseHook[]
  afterGenerateHook?: AfterGenerateHook | AfterGenerateHook[]
}

Einfache Plugin-Beispiele ​

Nur GET-Anfragen berücksichtigen:

ts
const getOnlyPlugin: SSGPlugin = {
  beforeRequestHook: (req) => {
    if (req.method === 'GET') {
      return req
    }
    return false
  },
}

Nach Statuscode filtern:

ts
const statusFilterPlugin: SSGPlugin = {
  afterResponseHook: (res) => {
    if (res.status === 200 || res.status === 500) {
      return res
    }
    return false
  },
}

Erzeugte Dateien protokollieren:

ts
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:

ts
// 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:

ts
import app from './index'
import { toSSG } from 'hono/ssg'
import { sitemapPlugin } from './plugins'

toSSG(app, fs, {
  plugins: [
    getOnlyPlugin,
    statusFilterPlugin,
    logFilesPlugin,
    sitemapPlugin('https://example.com'),
  ],
})

Veröffentlicht unter der MIT-Lizenz.