JSX
Model-written
contextclaimmethodsetupresultlimitation
JSX
Write HTML with JSX syntax via hono/jsx
You can write HTML with JSX syntax with hono/jsx.You can write HTML with JSX syntax with hono/jsx.Write HTML with JSX syntax via hono/jsx This is the page's premise for server and client renderingWrite HTML with JSX syntax via hono/jsxJSX syntax
Although hono/jsx works on the client, you will probably use it most often when rendering content on the server side. Here are some things related to JSX that are common to both server and client.Although hono/jsx works on the client, you will probably use it most often when rendering content on the server side. Here are some things related to JSX that are common to both server and client.hono/jsx works on client but is mostly used server side The shared JSX features described apply to both environmentshono/jsx works on client but is mostly used server sideclient server side
Settings
Configure tsconfig or deno.json for hono/jsx
To use JSX, modify the tsconfig.json:To use JSX, modify the tsconfig.json:Modify tsconfig.json to use JSX It sets the compiler option needed for JSX supportModify tsconfig.json to use JSXtsconfig.json
tsconfig.json:
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx"
}
}Alternatively, use the pragma directives:Alternatively, use the pragma directives:Pragma directives are an alternative to tsconfig settings They declare jsx and jsxImportSource inline per filePragma directives are an alternative to tsconfig settingsPragma directives
/** @jsx jsx */
/** @jsxImportSource hono/jsx */For Deno, you have to modify the deno.json instead of the tsconfig.json:For Deno, you have to modify the deno.json instead of the tsconfig.json:Deno users modify deno.json instead of tsconfig.json It uses precompile jsx mode and the @hono/hono/jsx sourceDeno users modify deno.json instead of tsconfig.jsondeno.json
{
"compilerOptions": {
"jsx": "precompile",
"jsxImportSource": "@hono/hono/jsx"
}
}Usage
Rename entry to .tsx and render components with c.html
Note
If you are coming straight from the Quick Start, the main file has a .ts extension - you need to change it to .tsx - otherwise you will not be able to run the application at all. You should additionally modify the package.json (or deno.json if you are using Deno) to reflect that change (e.g. instead of having bun run --hot src/index.ts in dev script, you should have bun run --hot src/index.tsx).If you are coming straight from the Quick Start, the main file has a .ts extension - you need to change it to .tsx - otherwise you will not be able to run the application at all. You should additionally modify the package.json (or deno.json if you are using Deno) to reflect that change (e.g. instead of having bun run --hot src/index.ts in dev script, you should have bun run --hot src/index.tsx).Rename main file to .tsx or the app will not run Update the dev script in package.json or deno.json to matchRename main file to .tsx or the app will not runmain file
index.tsx:
import { Hono } from 'hono'
import type { FC } from 'hono/jsx'
const app = new Hono()
const Layout: FC = (props) => {
return (
<html>
<body>{props.children}</body>
</html>
)
}
const Top: FC<{ messages: string[] }> = (props: {
messages: string[]
}) => {
return (
<Layout>
<h1>Hello Hono!</h1>
<ul>
{props.messages.map((message) => {
return <li>{message}!!</li>
})}
</ul>
</Layout>
)
}
app.get('/', (c) => {
const messages = ['Good Morning', 'Good Evening', 'Good Night']
return c.html(<Top messages={messages} />)
})
export default appMetadata hoisting
Title, link and meta tags hoist automatically to head
You can write document metadata tags such as <title>, <link>, and <meta> directly inside your components. These tags will be automatically hoisted to the <head> section of the document. This is especially useful when the <head> element is rendered far from the component that determines the appropriate metadata.You can write document metadata tags such as <title>, <link>, and <meta> directly inside your components. These tags will be automatically hoisted to the <head> section of the document. This is especially useful when the <head> element is rendered far from the component that determines the appropriate metadata.title, link and meta hoist automatically to the head Useful when head renders far from the component setting metadatatitle, link and meta hoist automatically to the headtitle link meta
import { Hono } from 'hono'
const app = new Hono()
app.use('*', async (c, next) => {
c.setRenderer((content) => {
return c.html(
<html>
<head></head>
<body>{content}</body>
</html>
)
})
await next()
})
app.get('/about', (c) => {
return c.render(
<>
<title>About Page</title>
<meta name='description' content='This is the about page.' />
about page content
</>
)
})
export default appNote
When hoisting occurs, existing elements are not removed. Elements appearing later are added to the end. For example, if you have <title>Default</title> in your <head> and a component renders <title>Page Title</title>, both titles will appear in the head.When hoisting occurs, existing elements are not removed. Elements appearing later are added to the end. For example, if you have <title>Default</title> in your <head> and a component renders <title>Page Title</title>, both titles will appear in the head.Hoisting keeps existing elements, appending later ones after So a default and a page title both appear in the headHoisting keeps existing elements, appending later ones afterHoisting
Fragment
Use Fragment or <> to group elements without extra nodes
Use Fragment to group multiple elements without adding extra nodes:Use Fragment to group multiple elements without adding extra nodes:Fragment groups multiple elements without extra nodes Import it from hono/jsx to wrap childrenFragment groups multiple elements without extra nodesFragment
import { Fragment } from 'hono/jsx'
const List = () => (
<Fragment>
<p>first child</p>
<p>second child</p>
<p>third child</p>
</Fragment>
)Or you can write it with <></> if it sets up properly.Or you can write it with <></> if it sets up properly.Use <> shorthand for Fragment when set up properly It avoids importing Fragment explicitlyUse <> shorthand for Fragment when set up properlyshorthand Fragment
const List = () => (
<>
<p>first child</p>
<p>second child</p>
<p>third child</p>
</>
)PropsWithChildren
PropsWithChildren infers child elements in function components
You can use PropsWithChildren to correctly infer a child element in a function component.You can use PropsWithChildren to correctly infer a child element in a function component.PropsWithChildren infers child elements in function components Wrap the props type so children are typed correctlyPropsWithChildren infers child elements in function componentsPropsWithChildren
import { PropsWithChildren } from 'hono/jsx'
type Post = {
id: number
title: string
}
function Component({ title, children }: PropsWithChildren<Post>) {
return (
<div>
<h1>{title}</h1>
{children}
</div>
)
}Inserting Raw HTML
dangerouslySetInnerHTML inserts raw HTML directly
To directly insert HTML, use dangerouslySetInnerHTML:To directly insert HTML, use dangerouslySetInnerHTML:dangerouslySetInnerHTML inserts raw HTML directly Pass an object with __html holding the markupdangerouslySetInnerHTML inserts raw HTML directlydangerouslySetInnerHTML
app.get('/foo', (c) => {
const inner = { __html: 'JSX · SSR' }
const Div = <div dangerouslySetInnerHTML={inner} />
})Memoization
memo optimizes components by memoizing computed strings
Optimize your components by memoizing computed strings using memo:Optimize your components by memoizing computed strings using memo:memo optimizes components by memoizing computed strings Wrap components in memo imported from hono/jsxmemo optimizes components by memoizing computed stringsmemo
import { memo } from 'hono/jsx'
const Header = memo(() => <header>Welcome to Hono</header>)
const Footer = memo(() => <footer>Powered by Hono</footer>)
const Layout = (
<div>
<Header />
<p>Hono is cool!</p>
<Footer />
</div>
)Context
useContext shares data across the component tree
By using useContext, you can share data globally across any level of the Component tree without passing values through props.By using useContext, you can share data globally across any level of the Component tree without passing values through props.useContext shares data across the component tree Avoids passing values through props at every leveluseContext shares data across the component treeuseContext
import type { FC } from 'hono/jsx'
import { createContext, useContext } from 'hono/jsx'
const themes = {
light: {
color: '#000000',
background: '#eeeeee',
},
dark: {
color: '#ffffff',
background: '#222222',
},
}
const ThemeContext = createContext(themes.light)
const Button: FC = () => {
const theme = useContext(ThemeContext)
return <button style={theme}>Push!</button>
}
const Toolbar: FC = () => {
return (
<div>
<Button />
</div>
)
}
// ...
app.get('/', (c) => {
return c.html(
<div>
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
</div>
)
})Async Component
Async components with await are awaited by c.html automatically
hono/jsx supports an Async Component, so you can use async/await in your component.
If you render it with c.html(), it will await automatically.hono/jsx supports an Async Component, so you can use async/await in your component. If you render it with c.html(), it will await automatically.Async components use await and are awaited by c.html This enables async data fetching inside componentsAsync components use await and are awaited by c.htmlAsync Component
const AsyncComponent = async () => {
await new Promise((r) => setTimeout(r, 1000)) // sleep 1s
return <div>Done!</div>
}
app.get('/', (c) => {
return c.html(
<html>
<body>
<AsyncComponent />
</body>
</html>
)
})Suspense Experimental
Suspense renders fallback then awaited content via streams
The React-like Suspense feature is available.
If you wrap the async component with Suspense, the content in the fallback will be rendered first, and once the Promise is resolved, the awaited content will be displayed.
You can use it with renderToReadableStream().The React-like Suspense feature is available. If you wrap the async component with Suspense, the content in the fallback will be rendered first, and once the Promise is resolved, the awaited content will be displayed. You can use it with renderToReadableStream().Suspense renders fallback first, then awaited content Pair it with renderToReadableStream for streaming outputSuspense renders fallback first, then awaited contentSuspense
import { renderToReadableStream, Suspense } from 'hono/jsx/streaming'
//...
app.get('/', (c) => {
const stream = renderToReadableStream(
<html>
<body>
<Suspense fallback={<div>loading...</div>}>
<Component />
</Suspense>
</body>
</html>
)
return c.body(stream, {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
'Transfer-Encoding': 'chunked',
},
})
})ErrorBoundary Experimental
ErrorBoundary catches child errors and shows fallback
You can catch errors in child components using ErrorBoundary.You can catch errors in child components using ErrorBoundary.ErrorBoundary catches errors in child components Wrap components to intercept thrown errorsErrorBoundary catches errors in child componentsErrorBoundary
In the example below, it will show the content specified in fallback if an error occurs.In the example below, it will show the content specified in fallback if an error occurs.fallback content shows when an error occurs The thrown Error triggers the boundary's fallback renderingfallback content shows when an error occursfallback content
function SyncComponent() {
throw new Error('Error')
return <div>Hello</div>
}
app.get('/sync', async (c) => {
return c.html(
<html>
<body>
<ErrorBoundary fallback={<div>Out of Service</div>}>
<SyncComponent />
</ErrorBoundary>
</body>
</html>
)
})ErrorBoundary can also be used with async components and Suspense.ErrorBoundary can also be used with async components and Suspense.ErrorBoundary also works with async components and Suspense It wraps Suspense to catch async failures tooErrorBoundary also works with async components and SuspenseErrorBoundary
async function AsyncComponent() {
await new Promise((resolve) => setTimeout(resolve, 2000))
throw new Error('Error')
return <div>Hello</div>
}
app.get('/with-suspense', async (c) => {
return c.html(
<html>
<body>
<ErrorBoundary fallback={<div>Out of Service</div>}>
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
</ErrorBoundary>
</body>
</html>
)
})StreamingContext Experimental
StreamingContext adds scriptNonce to generated script tags for CSP
You can use StreamingContext to provide configuration for streaming components like Suspense and ErrorBoundary. This is useful for adding nonce values to script tags generated by these components for Content Security Policy (CSP).You can use StreamingContext to provide configuration for streaming components like Suspense and ErrorBoundary. This is useful for adding nonce values to script tags generated by these components for Content Security Policy (CSP).StreamingContext configures streaming components with scriptNonce for CSP It adds nonce values to scripts generated by Suspense and ErrorBoundaryStreamingContext configures streaming components with scriptNonce for CSPStreamingContext
import { Suspense, StreamingContext } from 'hono/jsx/streaming'
// ...
app.get('/', (c) => {
const stream = renderToReadableStream(
<html>
<body>
<StreamingContext
value={{ scriptNonce: 'random-nonce-value' }}
>
<Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</Suspense>
</StreamingContext>
</body>
</html>
)
return c.body(stream, {
headers: {
'Content-Type': 'text/html; charset=UTF-8',
'Transfer-Encoding': 'chunked',
'Content-Security-Policy':
"script-src 'nonce-random-nonce-value'",
},
})
})The scriptNonce value will be automatically added to any <script> tags generated by Suspense and ErrorBoundary components.The scriptNonce value will be automatically added to any <script> tags generated by Suspense and ErrorBoundary components.scriptNonce is auto-added to generated script tags Suspense and ErrorBoundary scripts inherit the configured noncescriptNonce is auto-added to generated script tagsscriptNonce
Integration with html Middleware
Combine JSX and html middleware for powerful templating
Combine the JSX and HTML middlewares for powerful templating. For in-depth details, consult the HTML middleware documentation.Combine the JSX and HTML middlewares for powerful templating. For in-depth details, consult the HTML middleware documentation.Combine JSX and HTML middleware for powerful templating See the HTML middleware docs for in-depth detailsCombine JSX and HTML middleware for powerful templatingHTML middleware
import { Hono } from 'hono'
import { html } from 'hono/html'
const app = new Hono()
interface SiteData {
title: string
children?: any
}
const Layout = (props: SiteData) =>
html`<!doctype html>
<html>
<head>
<title>${props.title}</title>
</head>
<body>
${props.children}
</body>
</html>`
const Content = (props: { siteData: SiteData; name: string }) => (
<Layout {...props.siteData}>
<h1>Hello {props.name}</h1>
</Layout>
)
app.get('/:name', (c) => {
const { name } = c.req.param()
const props = {
name: name,
siteData: {
title: 'JSX with html sample',
},
}
return c.html(<Content {...props} />)
})
export default appWith JSX Renderer Middleware
JSX Renderer Middleware makes creating HTML pages easier
The JSX Renderer Middleware allows you to create HTML pages more easily with the JSX.The JSX Renderer Middleware allows you to create HTML pages more easily with the JSX.JSX Renderer Middleware makes creating HTML pages easier It wraps JSX rendering as reusable middlewareJSX Renderer Middleware makes creating HTML pages easierJSX Renderer Middleware
Override type definitions
Override type definitions to add custom elements and attributes
You can override the type definition to add your custom elements and attributes.You can override the type definition to add your custom elements and attributes.Override type definitions to add custom elements and attributes Declare module hono/jsx and extend JSX IntrinsicElementsOverride type definitions to add custom elements and attributestype definitions
declare module 'hono/jsx' {
namespace JSX {
interface IntrinsicElements {
'my-custom-element': HTMLAttributes & {
'x-event'?: 'click' | 'scroll'
}
}
}
}