Skip to main content

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 app

Metadata 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 app

Note

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 &middot; 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 app

With 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'
      }
    }
  }
}