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 | undefined
title?: string
}
}
The following is an example of app/routes/_renderer.tsx
.
export default function reactRenderer(component?: React.FC<ComponentProps>, options?: RendererOptions): MiddlewareHandler
reactRenderer(({ children: React.ReactElement<unknown, string | React.JSXElementConstructor<any>>
children, title: string | undefined
title }) => {
return (
<React.JSX.IntrinsicElements.html: React.DetailedHTMLProps<React.HtmlHTMLAttributes<HTMLHtmlElement>, HTMLHtmlElement>
html React.HTMLAttributes<HTMLHtmlElement>.lang?: string | undefined
lang='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 | undefined
charSet='UTF-8' />
<React.JSX.IntrinsicElements.meta: React.DetailedHTMLProps<React.MetaHTMLAttributes<HTMLMetaElement>, HTMLMetaElement>
meta React.MetaHTMLAttributes<HTMLMetaElement>.name?: string | undefined
name='viewport' React.MetaHTMLAttributes<HTMLMetaElement>.content?: string | undefined
content='width=device-width, initial-scale=1.0' />
{import.meta.ImportMeta.env: ImportMetaEnv
env.ImportMetaEnv.PROD: boolean
PROD ? (
<React.JSX.IntrinsicElements.script: React.DetailedHTMLProps<React.ScriptHTMLAttributes<HTMLScriptElement>, HTMLScriptElement>
script React.ScriptHTMLAttributes<HTMLScriptElement>.type?: string | undefined
type='module' React.ScriptHTMLAttributes<HTMLScriptElement>.src?: string | undefined
src='/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 | undefined
type='module' React.ScriptHTMLAttributes<HTMLScriptElement>.src?: string | undefined
src='/app/client.ts'></React.JSX.IntrinsicElements.script: React.DetailedHTMLProps<React.ScriptHTMLAttributes<HTMLScriptElement>, HTMLScriptElement>
script>
)}
{title: string | undefined
title ? <React.JSX.IntrinsicElements.title: React.DetailedHTMLProps<React.HTMLAttributes<HTMLTitleElement>, HTMLTitleElement>
title>{title: string
title}</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 | undefined
hydrate: async (elem: Node
elem, root: Element
root) => {
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 />)
```hydrateRoot(root: Element
root, elem: Node
elem 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.ReactNode) // https://github.com/honojs/honox/issues/87
},
createElement?: CreateElement | undefined
createElement: async (type: any
type: any, props: any
props: 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: any
type, props: any
props) 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) => Plugin
build 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: string
mode }) => {
if (mode: string
mode === 'client') {
return {
UserConfig.build?: BuildOptions | undefined
Build specific optionsbuild: {
BuildOptions.rollupOptions?: RollupOptions | undefined
Will be merged with internal rollup options.
https://rollupjs.org/configuration-options/rollupOptions: {
InputOptions.input?: InputOption | undefined
input: ['./app/client.ts'],
RollupOptions.output?: OutputOptions | OutputOptions[] | undefined
output: {
OutputOptions.entryFileNames?: string | ((chunkInfo: PreRenderedChunk) => string) | undefined
entryFileNames: 'static/client.js',
OutputOptions.chunkFileNames?: string | ((chunkInfo: PreRenderedChunk) => string) | undefined
chunkFileNames: 'static/assets/[name]-[hash].js',
OutputOptions.assetFileNames?: string | ((chunkInfo: PreRenderedAsset) => string) | undefined
assetFileNames: 'static/assets/[name].[ext]',
},
},
BuildOptions.emptyOutDir?: boolean | null | undefined
Empty outDir on write.emptyOutDir: false,
},
}
} else {
return {
UserConfig.ssr?: SSROptions | undefined
SSR specific optionsssr: {
SSROptions.external?: true | string[] | undefined
external: ['react', 'react-dom'],
},
UserConfig.plugins?: PluginOption[] | undefined
Array of vite plugins to use.plugins: [function honox(options?: Options): PluginOption[]
honox(), function build(pluginOptions?: CloudflarePagesBuildOptions): Plugin
build()],
}
}
})
Adjust tsconfig.json
jsx factory function option.
// @filename: tsconfig.json
{
'compilerOptions': {
// ...
"jsxImportSource": "react"
// ...
}
}