JSX-Renderer-Middleware
Mit der JSX-Renderer-Middleware kannst du das Layout beim Rendern von JSX mit c.render() festlegen, ohne c.setRenderer() zu verwenden. Außerdem ermöglicht sie mit useRequestContext() den Zugriff auf Context-Instanzen innerhalb von Komponenten.
Import
import { Hono } from 'hono'
import { jsxRenderer, useRequestContext } from 'hono/jsx-renderer'Verwendung
const app = new Hono()
app.get(
'/page/*',
jsxRenderer(({ children }) => {
return (
<html>
<body>
<header>Menu</header>
<div>{children}</div>
</body>
</html>
)
})
)
app.get('/page/about', (c) => {
return c.render(<h1>About me!</h1>)
})Optionen
optional docType: boolean | string
Wenn du am Anfang des HTML keinen DOCTYPE hinzufügen möchtest, setze die Option docType auf false.
app.use(
'*',
jsxRenderer(
({ children }) => {
return (
<html>
<body>{children}</body>
</html>
)
},
{ docType: false }
)
)Du kannst den DOCTYPE auch ausdrücklich festlegen.
app.use(
'*',
jsxRenderer(
({ children }) => {
return (
<html>
<body>{children}</body>
</html>
)
},
{
docType:
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">',
}
)
)optional stream: boolean | Record<string, string>
Wenn du true oder einen Record-Wert angibst, wird als Streaming-Antwort gerendert.
const AsyncComponent = async () => {
await new Promise((r) => setTimeout(r, 1000)) // sleep 1s
return <div>Hi!</div>
}
app.get(
'*',
jsxRenderer(
({ children }) => {
return (
<html>
<body>
<h1>SSR Streaming</h1>
{children}
</body>
</html>
)
},
{ stream: true }
)
)
app.get('/', (c) => {
return c.render(
<Suspense fallback={<div>loading...</div>}>
<AsyncComponent />
</Suspense>
)
})Bei true werden die folgenden Header hinzugefügt:
{
'Transfer-Encoding': 'chunked',
'Content-Type': 'text/html; charset=UTF-8',
'Content-Encoding': 'Identity'
}Du kannst die Header-Werte anpassen, indem du Record-Werte angibst.
Funktionsbasierte Optionen
Statt eines statischen Optionsobjekts kannst du eine Funktion übergeben, die ein Context-Objekt erhält. So kannst du Optionen dynamisch anhand des Anfragekontexts festlegen, etwa Umgebungsvariablen oder Anfrageparametern.
app.use(
'*',
jsxRenderer(
({ children }) => {
return (
<html>
<body>{children}</body>
</html>
)
},
(c) => ({
stream: c.req.header('X-Enable-Streaming') === 'true',
})
)
)Ein konkretes Beispiel ist das Deaktivieren von Streaming bei der statischen Website-Generierung (SSG) mit <Suspense>. Dafür kannst du den Helfer isSSGContext verwenden:
app.use(
'*',
jsxRenderer(
({ children }) => {
return (
<div>
<Suspense fallback={'loading...'}>
<Component />
</Suspense>
</div>
)
},
(c) => ({
stream: !isSSGContext(c),
})
)
)Verschachtelte Layouts
Die Komponente Layout ermöglicht das Verschachteln von Layouts.
app.use(
jsxRenderer(({ children }) => {
return (
<html>
<body>{children}</body>
</html>
)
})
)
const blog = new Hono()
blog.use(
jsxRenderer(({ children, Layout }) => {
return (
<Layout>
<nav>Blog Menu</nav>
<div>{children}</div>
</Layout>
)
})
)
app.route('/blog', blog)useRequestContext()
useRequestContext() gibt eine Instanz von Context zurück.
import { useRequestContext, jsxRenderer } from 'hono/jsx-renderer'
const app = new Hono()
app.use(jsxRenderer())
const RequestUrlBadge: FC = () => {
const c = useRequestContext()
return <b>{c.req.url}</b>
}
app.get('/page/info', (c) => {
return c.render(
<div>
You are accessing: <RequestUrlBadge />
</div>
)
})Achtung
Mit Denos JSX-Option precompile kannst du useRequestContext() nicht verwenden. Verwende stattdessen react-jsx:
"compilerOptions": {
"jsx": "precompile",
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}ContextRenderer erweitern
Wenn du ContextRenderer wie unten definierst, kannst du zusätzliche Inhalte an den Renderer übergeben. Das ist beispielsweise hilfreich, um den Inhalt des head-Tags abhängig von der Seite zu ändern.
declare module 'hono' {
interface ContextRenderer {
(
content: string | Promise<string>,
props: { title: string }
): Response
}
}
const app = new Hono()
app.get(
'/page/*',
jsxRenderer(({ children, title }) => {
return (
<html>
<head>
<title>{title}</title>
</head>
<body>
<header>Menu</header>
<div>{children}</div>
</body>
</html>
)
})
)
app.get('/page/favorites', (c) => {
return c.render(
<div>
<ul>
<li>Eating sushi</li>
<li>Watching baseball games</li>
</ul>
</div>,
{
title: 'My favorites',
}
)
})