React Renderer

You can define a renderer using @hono/react-renderer. Install the modules first.

npm i @hono/react-renderer react react-dom hono
npm i -D @types/react @types/react-dom

Define the Props that the renderer will receive in global.d.ts.

// @filename: global.d.ts
import '@hono/react-renderer'

declare module '@hono/react-renderer' {
  interface Props {
    Props.title?: string | undefinedtitle?: string
  }
}

The following is an example of app/routes/_renderer.tsx.

export default function reactRenderer(component?: React.FC<ComponentProps>, options?: RendererOptions): MiddlewareHandlerreactRenderer(({ children: React.ReactElement<unknown, string | React.JSXElementConstructor<any>>children, title: string | undefinedtitle }) => {
  return (
    <React.JSX.IntrinsicElements.html: React.DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLHtmlElement>, HTMLHtmlElement>html React.HTMLAttributes<HTMLHtmlElement>.lang?: string | undefinedlang='en'>
      <React.JSX.IntrinsicElements.head: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadElement>, HTMLHeadElement>head>
        <React.JSX.IntrinsicElements.meta: React.DetailedHTMLProps<React.MetaHTMLAttributes<HTMLMetaElement>, HTMLMetaElement>meta React.MetaHTMLAttributes<HTMLMetaElement>.charSet?: string | undefinedcharSet='UTF-8' />
        <React.JSX.IntrinsicElements.meta: React.DetailedHTMLProps<React.MetaHTMLAttributes<HTMLMetaElement>, HTMLMetaElement>meta React.MetaHTMLAttributes<HTMLMetaElement>.name?: string | undefinedname='viewport' React.MetaHTMLAttributes<HTMLMetaElement>.content?: string | undefinedcontent='width=device-width, initial-scale=1.0' />
        {import.meta.ImportMeta.env: ImportMetaEnvenv.ImportMetaEnv.PROD: booleanPROD ? (
          <React.JSX.IntrinsicElements.script: React.DetailedHTMLProps<React.ScriptHTMLAttributes<HTMLScriptElement>, HTMLScriptElement>script React.ScriptHTMLAttributes<HTMLScriptElement>.type?: string | undefinedtype='module' React.ScriptHTMLAttributes<HTMLScriptElement>.src?: string | undefinedsrc='/static/client.js'></React.JSX.IntrinsicElements.script: React.DetailedHTMLProps<React.ScriptHTMLAttributes<HTMLScriptElement>, HTMLScriptElement>script>
        ) : (
          <React.JSX.IntrinsicElements.script: React.DetailedHTMLProps<React.ScriptHTMLAttributes<HTMLScriptElement>, HTMLScriptElement>script React.ScriptHTMLAttributes<HTMLScriptElement>.type?: string | undefinedtype='module' React.ScriptHTMLAttributes<HTMLScriptElement>.src?: string | undefinedsrc='/app/client.ts'></React.JSX.IntrinsicElements.script: React.DetailedHTMLProps<React.ScriptHTMLAttributes<HTMLScriptElement>, HTMLScriptElement>script>
        )}
        {title: string | undefinedtitle ? <React.JSX.IntrinsicElements.title: React.DetailedHTMLProps<React.HTMLAttributes<HTMLTitleElement>, HTMLTitleElement>title>{title: stringtitle}</React.JSX.IntrinsicElements.title: React.DetailedHTMLProps<React.HTMLAttributes<HTMLTitleElement>, HTMLTitleElement>title> : ''}
      </React.JSX.IntrinsicElements.head: React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadElement>, HTMLHeadElement>head>
      <React.JSX.IntrinsicElements.body: React.DetailedHTMLProps<React.HTMLAttributes<HTMLBodyElement>, HTMLBodyElement>body>{children: React.ReactElement<unknown, string | React.JSXElementConstructor<any>>children}</React.JSX.IntrinsicElements.body: React.DetailedHTMLProps<React.HTMLAttributes<HTMLBodyElement>, HTMLBodyElement>body>
    </React.JSX.IntrinsicElements.html: React.DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLHtmlElement>, HTMLHtmlElement>html>
  )
})

The app/client.ts will be like this.

// @filename: app/client.ts
import { const createClient: (options?: ClientOptions) => Promise<void>createClient } from 'honox/client'
import type React from 'react'

function createClient(options?: ClientOptions): Promise<void>createClient({
  hydrate?: Hydrate | undefinedhydrate: async (elem: Nodeelem, root: Elementroot) => {
    const { const hydrateRoot: (container: Element | Document, initialChildren: React.ReactNode, options?: HydrationOptions) => Root
Same as `createRoot()`, but is used to hydrate a container whose HTML contents were rendered by ReactDOMServer. React will attempt to attach event listeners to the existing markup. **Example Usage** ```jsx hydrateRoot(document.querySelector('#root'), <App />) ```
hydrateRoot
} = await import('react-dom/client')
const hydrateRoot: (container: Element | Document, initialChildren: React.ReactNode, options?: HydrationOptions) => Root
Same as `createRoot()`, but is used to hydrate a container whose HTML contents were rendered by ReactDOMServer. React will attempt to attach event listeners to the existing markup. **Example Usage** ```jsx hydrateRoot(document.querySelector('#root'), <App />) ```
@seehttps://reactjs.org/docs/react-dom-client.html#hydrateroot
hydrateRoot
(root: Elementroot, elem: Nodeelem as any as React.type React.ReactNode = string | number | bigint | boolean | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | React.ReactPortal | Promise<...> | null | undefined
Represents all of the things React can render. Where {@link ReactElement } only represents JSX, `ReactNode` represents everything that can be rendered.
@see{@link https://react-typescript-cheatsheet.netlify.app/docs/react-types/reactnode/ React TypeScript Cheatsheet}@example```tsx // Typing children type Props = { children: ReactNode } const Component = ({ children }: Props) => <div>{children}</div> <Component>hello</Component> ```@example```tsx // Typing a custom element type Props = { customElement: ReactNode } const Component = ({ customElement }: Props) => <div>{customElement}</div> <Component customElement={<div>hello</div>} /> ```
ReactNode
) // https://github.com/honojs/honox/issues/87
}, createElement?: CreateElement | undefinedcreateElement: async (type: anytype: any, props: anyprops: any) => { const {
const createElement: {
    (type: "input", props?: (React.InputHTMLAttributes<HTMLInputElement> & React.ClassAttributes<HTMLInputElement>) | null, ...children: React.ReactNode[]): React.DetailedReactHTMLElement<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
    <P extends React.HTMLAttributes<T>, T extends HTMLElement>(type: React.HTMLElementType, props?: (React.ClassAttributes<T> & P) | null, ...children: React.ReactNode[]): React.DetailedReactHTMLElement<P, T>;
    <P extends React.SVGAttributes<T>, T extends SVGElement>(type: React.SVGElementType, props?: (React.ClassAttributes<T> & P) | null, ...children: React.ReactNode[]): React.ReactSVGElement;
    <P extends React.DOMAttributes<T>, T extends Element>(type: string, props?: (React.ClassAttributes<T> & P) | null, ...children: React.ReactNode[]): React.DOMElement<P, T>;
    <P extends {}>(type: React.FunctionComponent<P>, props?: (React.Attributes & P) | null, ...children: React.ReactNode[]): React.FunctionComponentElement<P>;
    <P extends {}, T extends React.Component<P, React.ComponentState>, C extends React.ComponentClass<P>>(type: React.ClassType<P, T, C>, props?: (React.ClassAttributes<T> & P) | null, ...children: React.ReactNode[]): React.CElement<P, T>;
    <P extends {}>(type: React.FunctionComponent<P> | React.ComponentClass<P> | string, props?: (React.Attributes & P) | null, ...children: React.ReactNode[]): React.ReactElement<P>;
}
createElement
} = await import('react')
return const createElement: <any, React.Component<any, any, any>, any>(type: any, props?: any, ...children: React.ReactNode[]) => React.CElement<any, React.Component<any, any, any>> (+6 overloads)createElement(type: anytype, props: anyprops) as any as Node // https://github.com/honojs/honox/issues/87 }, })

Configure react in vite.config.ts.

// @filename: vite.config.ts
import const build: (pluginOptions?: CloudflarePagesBuildOptions) => Pluginbuild from '@hono/vite-build/cloudflare-pages'
import function honox(options?: Options): PluginOption[]honox from 'honox/vite'
import { function defineConfig(config: UserConfig): UserConfig (+3 overloads)
Type helper to make it easier to use vite.config.ts accepts a direct {@link UserConfig } object, or a function that returns it. The function receives a {@link ConfigEnv } object.
defineConfig
} from 'vite'
export default function defineConfig(config: UserConfigFnObject): UserConfigFnObject (+3 overloads)
Type helper to make it easier to use vite.config.ts accepts a direct {@link UserConfig } object, or a function that returns it. The function receives a {@link ConfigEnv } object.
defineConfig
(({ mode: stringmode }) => {
if (mode: stringmode === 'client') { return { UserConfig.build?: BuildOptions | undefined
Build specific options
build
: {
BuildOptions.rollupOptions?: RollupOptions | undefined
Will be merged with internal rollup options. https://rollupjs.org/configuration-options/
rollupOptions
: {
InputOptions.input?: InputOption | undefinedinput: ['./app/client.ts'], RollupOptions.output?: OutputOptions | OutputOptions[] | undefinedoutput: { OutputOptions.entryFileNames?: string | ((chunkInfo: PreRenderedChunk) => string) | undefinedentryFileNames: 'static/client.js', OutputOptions.chunkFileNames?: string | ((chunkInfo: PreRenderedChunk) => string) | undefinedchunkFileNames: 'static/assets/[name]-[hash].js', OutputOptions.assetFileNames?: string | ((chunkInfo: PreRenderedAsset) => string) | undefinedassetFileNames: 'static/assets/[name].[ext]', }, }, BuildOptions.emptyOutDir?: boolean | null | undefined
Empty outDir on write.
@defaulttrue when outDir is a sub directory of project root
emptyOutDir
: false,
}, } } else { return { UserConfig.ssr?: SSROptions | undefined
SSR specific options
ssr
: {
SSROptions.external?: true | string[] | undefinedexternal: ['react', 'react-dom'], }, UserConfig.plugins?: PluginOption[] | undefined
Array of vite plugins to use.
plugins
: [function honox(options?: Options): PluginOption[]honox(), function build(pluginOptions?: CloudflarePagesBuildOptions): Pluginbuild()],
} } })

Adjust tsconfig.json jsx factory function option.

// @filename: tsconfig.json
{
  'compilerOptions': {
    // ...
    "jsxImportSource": "react"
    // ...
  }
}