Get Started - with Client

Let's create an application that includes a client side. Here, we will use hono/jsx/dom.

Project Structure

Below is the project structure of a minimal application including a client side:

.
├── app
│   ├── client.ts // client entry file
│   ├── global.d.ts
│   ├── islands
│   │   └── counter.tsx // island component
│   ├── routes
│   │   ├── _renderer.tsx
│   │   └── index.tsx
│   └── server.ts
├── package.json
├── tsconfig.json
└── vite.config.ts

Renderer

This is a _renderer.tsx, which will load the /app/client.ts entry file for the client. It will load the JavaScript file for production according to the variable import.meta.env.PROD. And renders the inside of <HasIslands /> if there are islands on that page.

import { const jsxRenderer: (component?: ComponentWithChildren, options?: RendererOptions) => MiddlewareHandler
JSX Renderer Middleware for hono.
@see{@link https://hono.dev/docs/middleware/builtin/jsx-renderer}@paramcomponent - The component to render, which can accept children and props.@paramoptions - The options for the JSX renderer middleware.@paramoptions.docType - The DOCTYPE to be added at the beginning of the HTML. If set to false, no DOCTYPE will be added.@paramoptions.stream - If set to true, enables streaming response with default headers. If a record is provided, custom headers will be used.@returnsThe middleware handler function.@example```ts 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>) }) ```
jsxRenderer
} from 'hono/jsx-renderer'
import {
const HasIslands: ({ children }: {
    children: any;
}) => any
HasIslands
} from 'honox/server'
export default function jsxRenderer(component?: ComponentWithChildren, options?: RendererOptions): MiddlewareHandler
JSX Renderer Middleware for hono.
@see{@link https://hono.dev/docs/middleware/builtin/jsx-renderer}@paramcomponent - The component to render, which can accept children and props.@paramoptions - The options for the JSX renderer middleware.@paramoptions.docType - The DOCTYPE to be added at the beginning of the HTML. If set to false, no DOCTYPE will be added.@paramoptions.stream - If set to true, enables streaming response with default headers. If a record is provided, custom headers will be used.@returnsThe middleware handler function.@example```ts 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>) }) ```
jsxRenderer
(({ children: Childchildren }) => {
return ( <JSX.IntrinsicElements.html: HtmlHTMLAttributeshtml JSX.HTMLAttributes.lang?: string | undefinedlang='en'> <JSX.IntrinsicElements.head: JSX.HTMLAttributeshead> <JSX.IntrinsicElements.meta: MetaHTMLAttributesmeta MetaHTMLAttributes.charset?: StringLiteralUnion<"utf-8"> | undefinedcharset='UTF-8' /> <JSX.IntrinsicElements.meta: MetaHTMLAttributesmeta MetaHTMLAttributes.name?: StringLiteralUnion<MetaName> | undefinedname='viewport' MetaHTMLAttributes.content?: string | undefinedcontent='width=device-width, initial-scale=1.0' /> {import.meta.ImportMeta.env: ImportMetaEnvenv.ImportMetaEnv.PROD: booleanPROD ? ( <
const HasIslands: ({ children }: {
    children: any;
}) => any
HasIslands
>
<JSX.IntrinsicElements.script: ScriptHTMLAttributesscript ScriptHTMLAttributes.type?: StringLiteralUnion<"" | "module" | "text/javascript" | "importmap"> | undefined
@seehttps://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type
type
='module' ScriptHTMLAttributes.src?: string | undefinedsrc='/static/client.js'></JSX.IntrinsicElements.script: ScriptHTMLAttributesscript>
</
const HasIslands: ({ children }: {
    children: any;
}) => any
HasIslands
>
) : ( <JSX.IntrinsicElements.script: ScriptHTMLAttributesscript ScriptHTMLAttributes.type?: StringLiteralUnion<"" | "module" | "text/javascript" | "importmap"> | undefined
@seehttps://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type
type
='module' ScriptHTMLAttributes.src?: string | undefinedsrc='/app/client.ts'></JSX.IntrinsicElements.script: ScriptHTMLAttributesscript>
)} </JSX.IntrinsicElements.head: JSX.HTMLAttributeshead> <JSX.IntrinsicElements.body: JSX.HTMLAttributesbody>{children: Childchildren}</JSX.IntrinsicElements.body: JSX.HTMLAttributesbody> </JSX.IntrinsicElements.html: HtmlHTMLAttributeshtml> ) })

If you have a manifest file in dist/.vite/manifest.json, you can easily write it using <Script />.

import { const jsxRenderer: (component?: ComponentWithChildren, options?: RendererOptions) => MiddlewareHandler
JSX Renderer Middleware for hono.
@see{@link https://hono.dev/docs/middleware/builtin/jsx-renderer}@paramcomponent - The component to render, which can accept children and props.@paramoptions - The options for the JSX renderer middleware.@paramoptions.docType - The DOCTYPE to be added at the beginning of the HTML. If set to false, no DOCTYPE will be added.@paramoptions.stream - If set to true, enables streaming response with default headers. If a record is provided, custom headers will be used.@returnsThe middleware handler function.@example```ts 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>) }) ```
jsxRenderer
} from 'hono/jsx-renderer'
import { const Script: (options: Options) => anyScript } from 'honox/server' export default function jsxRenderer(component?: ComponentWithChildren, options?: RendererOptions): MiddlewareHandler
JSX Renderer Middleware for hono.
@see{@link https://hono.dev/docs/middleware/builtin/jsx-renderer}@paramcomponent - The component to render, which can accept children and props.@paramoptions - The options for the JSX renderer middleware.@paramoptions.docType - The DOCTYPE to be added at the beginning of the HTML. If set to false, no DOCTYPE will be added.@paramoptions.stream - If set to true, enables streaming response with default headers. If a record is provided, custom headers will be used.@returnsThe middleware handler function.@example```ts 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>) }) ```
jsxRenderer
(({ children: Childchildren }) => {
return ( <JSX.IntrinsicElements.html: HtmlHTMLAttributeshtml JSX.HTMLAttributes.lang?: string | undefinedlang='en'> <JSX.IntrinsicElements.head: JSX.HTMLAttributeshead> <JSX.IntrinsicElements.meta: MetaHTMLAttributesmeta MetaHTMLAttributes.charset?: StringLiteralUnion<"utf-8"> | undefinedcharset='UTF-8' /> <JSX.IntrinsicElements.meta: MetaHTMLAttributesmeta MetaHTMLAttributes.name?: StringLiteralUnion<MetaName> | undefinedname='viewport' MetaHTMLAttributes.content?: string | undefinedcontent='width=device-width, initial-scale=1.0' /> <const Script: (options: Options) => anyScript src: stringsrc='/app/client.ts' /> </JSX.IntrinsicElements.head: JSX.HTMLAttributeshead> <JSX.IntrinsicElements.body: JSX.HTMLAttributesbody>{children: Childchildren}</JSX.IntrinsicElements.body: JSX.HTMLAttributesbody> </JSX.IntrinsicElements.html: HtmlHTMLAttributeshtml> ) })

Note: Since <HasIslands /> can slightly affect build performance when used, it is recommended that you do not use it in the development environment, but only at build time. <Script /> does not cause performance degradation during development, so it's better to use it.

nonce Attribute

If you want to add a nonce attribute to <Script /> or <script /> element, you can use Security Headers Middleware.

Define the middleware:

// @filename: app/routes/_middleware.ts
import { const createRoute: CreateHandlersInterface<Env, any>createRoute } from 'honox/factory'
import { const secureHeaders: (customOptions?: SecureHeadersOptions) => MiddlewareHandler
Secure Headers Middleware for Hono.
@see{@link https://hono.dev/docs/middleware/builtin/secure-headers}@paramcustomOptions - The options for the secure headers middleware.@paramcustomOptions.contentSecurityPolicy - Settings for the Content-Security-Policy header.@paramcustomOptions.contentSecurityPolicyReportOnly - Settings for the Content-Security-Policy-Report-Only header.@paramcustomOptions.crossOriginEmbedderPolicy - Settings for the Cross-Origin-Embedder-Policy header.@paramcustomOptions.crossOriginResourcePolicy - Settings for the Cross-Origin-Resource-Policy header.@paramcustomOptions.crossOriginOpenerPolicy - Settings for the Cross-Origin-Opener-Policy header.@paramcustomOptions.originAgentCluster - Settings for the Origin-Agent-Cluster header.@paramcustomOptions.referrerPolicy - Settings for the Referrer-Policy header.@paramcustomOptions.reportingEndpoints - Settings for the Reporting-Endpoints header.@paramcustomOptions.reportTo - Settings for the Report-To header.@paramcustomOptions.strictTransportSecurity - Settings for the Strict-Transport-Security header.@paramcustomOptions.xContentTypeOptions - Settings for the X-Content-Type-Options header.@paramcustomOptions.xDnsPrefetchControl - Settings for the X-DNS-Prefetch-Control header.@paramcustomOptions.xDownloadOptions - Settings for the X-Download-Options header.@paramcustomOptions.xFrameOptions - Settings for the X-Frame-Options header.@paramcustomOptions.xPermittedCrossDomainPolicies - Settings for the X-Permitted-Cross-Domain-Policies header.@paramcustomOptions.xXssProtection - Settings for the X-XSS-Protection header.@paramcustomOptions.removePoweredBy - Settings for remove X-Powered-By header.@paramcustomOptions.permissionsPolicy - Settings for the Permissions-Policy header.@returnsThe middleware handler function.@example```ts const app = new Hono() app.use(secureHeaders()) ```
secureHeaders
, const NONCE: ContentSecurityPolicyOptionHandlerNONCE } from 'hono/secure-headers'
function secureHeaders(customOptions?: SecureHeadersOptions): MiddlewareHandler
Secure Headers Middleware for Hono.
@see{@link https://hono.dev/docs/middleware/builtin/secure-headers}@paramcustomOptions - The options for the secure headers middleware.@paramcustomOptions.contentSecurityPolicy - Settings for the Content-Security-Policy header.@paramcustomOptions.contentSecurityPolicyReportOnly - Settings for the Content-Security-Policy-Report-Only header.@paramcustomOptions.crossOriginEmbedderPolicy - Settings for the Cross-Origin-Embedder-Policy header.@paramcustomOptions.crossOriginResourcePolicy - Settings for the Cross-Origin-Resource-Policy header.@paramcustomOptions.crossOriginOpenerPolicy - Settings for the Cross-Origin-Opener-Policy header.@paramcustomOptions.originAgentCluster - Settings for the Origin-Agent-Cluster header.@paramcustomOptions.referrerPolicy - Settings for the Referrer-Policy header.@paramcustomOptions.reportingEndpoints - Settings for the Reporting-Endpoints header.@paramcustomOptions.reportTo - Settings for the Report-To header.@paramcustomOptions.strictTransportSecurity - Settings for the Strict-Transport-Security header.@paramcustomOptions.xContentTypeOptions - Settings for the X-Content-Type-Options header.@paramcustomOptions.xDnsPrefetchControl - Settings for the X-DNS-Prefetch-Control header.@paramcustomOptions.xDownloadOptions - Settings for the X-Download-Options header.@paramcustomOptions.xFrameOptions - Settings for the X-Frame-Options header.@paramcustomOptions.xPermittedCrossDomainPolicies - Settings for the X-Permitted-Cross-Domain-Policies header.@paramcustomOptions.xXssProtection - Settings for the X-XSS-Protection header.@paramcustomOptions.removePoweredBy - Settings for remove X-Powered-By header.@paramcustomOptions.permissionsPolicy - Settings for the Permissions-Policy header.@returnsThe middleware handler function.@example```ts const app = new Hono() app.use(secureHeaders()) ```
secureHeaders
({
SecureHeadersOptions.contentSecurityPolicy?: ContentSecurityPolicyOptions | undefinedcontentSecurityPolicy: { ContentSecurityPolicyOptions.scriptSrc?: ContentSecurityPolicyOptionValue | undefinedscriptSrc: [const NONCE: ContentSecurityPolicyOptionHandlerNONCE], }, })

You can get the nonce value with c.get('secureHeadersNonce'):

import { const jsxRenderer: (component?: ComponentWithChildren, options?: RendererOptions) => MiddlewareHandler
JSX Renderer Middleware for hono.
@see{@link https://hono.dev/docs/middleware/builtin/jsx-renderer}@paramcomponent - The component to render, which can accept children and props.@paramoptions - The options for the JSX renderer middleware.@paramoptions.docType - The DOCTYPE to be added at the beginning of the HTML. If set to false, no DOCTYPE will be added.@paramoptions.stream - If set to true, enables streaming response with default headers. If a record is provided, custom headers will be used.@returnsThe middleware handler function.@example```ts 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>) }) ```
jsxRenderer
} from 'hono/jsx-renderer'
import { const Script: (options: Options) => anyScript } from 'honox/server' export default function jsxRenderer(component?: ComponentWithChildren, options?: RendererOptions): MiddlewareHandler
JSX Renderer Middleware for hono.
@see{@link https://hono.dev/docs/middleware/builtin/jsx-renderer}@paramcomponent - The component to render, which can accept children and props.@paramoptions - The options for the JSX renderer middleware.@paramoptions.docType - The DOCTYPE to be added at the beginning of the HTML. If set to false, no DOCTYPE will be added.@paramoptions.stream - If set to true, enables streaming response with default headers. If a record is provided, custom headers will be used.@returnsThe middleware handler function.@example```ts 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>) }) ```
jsxRenderer
(({ children: Childchildren }, c: Context<any, any, {}>c) => {
return ( <JSX.IntrinsicElements.html: HtmlHTMLAttributeshtml JSX.HTMLAttributes.lang?: string | undefinedlang='en'> <JSX.IntrinsicElements.head: JSX.HTMLAttributeshead> <const Script: (options: Options) => anyScript src: stringsrc='/app/client.ts' async?: boolean | undefinedasync nonce?: string | undefinednonce={c: Context<any, any, {}>c.
Context<any, any, {}>.get: Get
<"secureHeadersNonce">(key: "secureHeadersNonce") => any (+1 overload)
get
('secureHeadersNonce')} />
</JSX.IntrinsicElements.head: JSX.HTMLAttributeshead> <JSX.IntrinsicElements.body: JSX.HTMLAttributesbody>{children: Childchildren}</JSX.IntrinsicElements.body: JSX.HTMLAttributesbody> </JSX.IntrinsicElements.html: HtmlHTMLAttributeshtml> ) })

Client Entry File

A client-side entry file should be in app/client.ts. Simply, write createClient().

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

function createClient(options?: ClientOptions): Promise<void>createClient()

Interactions

If you want to add interactions to your page, create Island components. Islands components should be:

For example, you can write an interactive component such as the following counter:

import { const useState: UseStateTypeuseState } from 'hono/jsx'

export default function function Counter(): JSX.ElementCounter() {
  const [const count: numbercount, const setCount: UpdateStateFunction<number>setCount] = useState<number>(initialState: number | (() => number)): [number, UpdateStateFunction<number>] (+1 overload)useState(0)
  return (
    <JSX.IntrinsicElements.div: JSX.HTMLAttributesdiv>
      <JSX.IntrinsicElements.p: JSX.HTMLAttributesp>Count: {const count: numbercount}</JSX.IntrinsicElements.p: JSX.HTMLAttributesp>
      <JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton EventAttributes.onClick?: ((event: MouseEvent) => void) | undefinedonClick={() => const setCount: (newState: number | ((currentState: number) => number)) => voidsetCount(const count: numbercount + 1)}>Increment</JSX.IntrinsicElements.button: ButtonHTMLAttributesbutton>
    </JSX.IntrinsicElements.div: JSX.HTMLAttributesdiv>
  )
}

// When you load the component in a route file, it is rendered as Server-Side rendering and JavaScript is also sent to the client side.

// @filename: app/routes/index.tsx
/** @jsx jsx */
/** @jsxImportSource hono/jsx */
import { const createRoute: CreateHandlersInterface<Env, any>createRoute } from 'honox/factory'
import function Counter(): JSX.ElementCounter from '../islands/counter'

export default createRoute<{}, Response | Promise<Response>, Env>(handler1: H<Env, any, {}, Response | Promise<Response>>): [...] (+9 overloads)createRoute((c: Context<Env, any, {}>c) => {
  return c: Context<Env, any, {}>c.
Context<Env, any, {}>.render: DefaultRenderer
(content: string | Promise<string>) => Response | Promise<Response>
`.render()` can create a response within a layout.
@see{@link https://hono.dev/docs/api/context#render-setrenderer}@example```ts app.get('/', (c) => { return c.render('Hello!') }) ```
render
(
<JSX.IntrinsicElements.div: JSX.HTMLAttributesdiv> <JSX.IntrinsicElements.h1: JSX.HTMLAttributesh1>Hello</JSX.IntrinsicElements.h1: JSX.HTMLAttributesh1> <function Counter(): JSX.ElementCounter /> </JSX.IntrinsicElements.div: JSX.HTMLAttributesdiv> ) })

Note: You cannot access a Context object in Island components. Therefore, you should pass the value from components outside of the Island.

import { const useRequestContext: <E extends Env = any, P extends string = any, I extends Input = {}>() => Context<E, P, I>
useRequestContext for Hono.
@templateE - The environment type.@templateP - The parameter type.@templateI - The input type.@returnsAn instance of Context.@example```ts 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> ) }) ```
useRequestContext
} from 'hono/jsx-renderer'
import
function Counter({ init }: {
    init: number;
}): JSX.Element
Counter
from '../islands/counter'
export default function function Component(): JSX.ElementComponent() { const const c: Context<any, any, {}>c = useRequestContext<any, any, {}>(): Context<any, any, {}>
useRequestContext for Hono.
@templateE - The environment type.@templateP - The parameter type.@templateI - The input type.@returnsAn instance of Context.@example```ts 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> ) }) ```
useRequestContext
()
return <
function Counter({ init }: {
    init: number;
}): JSX.Element
Counter
init: numberinit={function parseInt(string: string, radix?: number): number
Converts a string to an integer.
@paramstring A string to convert into a number.@paramradix A value between 2 and 36 that specifies the base of the number in `string`. If this argument is not supplied, strings with a prefix of '0x' are considered hexadecimal. All other strings are considered decimal.
parseInt
(const c: Context<any, any, {}>c.Context<any, any, {}>.req: HonoRequest<any, unknown>
`.req` is the instance of {@link HonoRequest } .
req
.HonoRequest<any, unknown>.query(key: string): string | undefined (+1 overload)
`.query()` can get querystring parameters.
@see{@link https://hono.dev/docs/api/request#query}@example```ts // Query params app.get('/search', (c) => { const query = c.req.query('q') }) // Get all params at once app.get('/search', (c) => { const { q, limit, offset } = c.req.query() }) ```
query
('count') ?? '0', 10)} />
}