HonoX Documentation

repository·main·Indexed 25 days ago

https://github.com/honojs/honox

A fast, lightweight meta-framework built on Hono and Vite for creating full-stack websites. HonoX features file-based routing, SSR, and Islands hydration. It supports custom renderers (including React, Preact, and Solid), nested layouts, and integration with Tailwind CSS and MDX.

Tokens
7.9K
Snippets
27
Records
50
Agent score
83%

What's inside HonoX

  1. Create a new HonoX project with starter template

    main

    Use the hono-create command to scaffold a new project. When prompted, select the x-basic option to use the basic starter template.

    npm create hono@latest
  2. Setup Tailwind CSS in HonoX

    main

    Since HonoX is Vite-centric, use the official Tailwind Vite instructions.

    1. CSS Setup: In app/style.css, explicitly set the base path for source detection: @import 'tailwindcss' source('../app');

    2. Renderer Integration: Import the CSS file in your renderer using the <Link /> component from honox/server.

    3. Vite Configuration: Add @tailwindcss/vite to your plugins and ensure the CSS file is included in the honox client input list.

    // vite.config.ts
    import honox from 'honox/vite'
    import { defineConfig } from 'vite'
    import build from '@hono/vite-build/cloudflare-workers'
    import tailwindcss from '@tailwindcss/vite'
    
    export default defineConfig({
      plugins: [
        honox({
          client: {
            input: [
              '/app/client.ts',
              '/app/style.css',
            ],
          },
        }),
        build(),
        tailwindcss(),
      ],
    })
  3. Add a nonce to Script components for Security

    main

    If using secureHeaders middleware, you can retrieve the nonce via c.get('secureHeadersNonce') and pass it to the <Script /> component.

    // app/routes/_renderer.tsx
    import { jsxRenderer } from 'hono/jsx-renderer'
    import { Script } from 'honox/server'
    
    export default jsxRenderer(({ children }, c) => {
      return (
        <html lang='en'>
          <head>
            <Script src='/app/client.ts' async nonce={c.get('secureHeadersNonce')} />
          </head>
          <body>{children}</body>
        </html>
      )
    })
  4. Enable Static Site Generation (SSG)

    main

    Use @hono/vite-ssg to generate static HTML for each route.

    To include client-side scripts and assets in SSG build:

    1. Use honox/vite/client in the client mode plugin list.
    2. Run the build command using both client and default modes: vite build --mode client && vite build
    // vite.config.ts
    import ssg from '@hono/vite-ssg'
    import honox from 'honox/vite'
    import client from 'honox/vite/client'
    import { defineConfig } from 'vite'
    
    export default defineConfig(({ mode }) => {
      if (mode === 'client') {
        return {
          plugins: [client()],
        }
      } else {
        return {
          build: {
            emptyOutDir: false,
          },
          plugins: [honox(), ssg({ entry: './app/server.ts' })],
        }
      }
    })
  5. Enable Runtime Environment Variables in HonoX

    main

    By default, Vite optimizes process.env to an empty object during the build process, which prevents process.env.MY_VAR from working in built applications. To ensure environment variables are available at runtime, add define: { 'process.env': 'process.env' } to your vite.config.ts.

    export default defineConfig({
      define: {
        'process.env': 'process.env', // <=== Add this line
      },
      plugins: [
        honox({
          devServer: { adapter },
          client: { input: ['/app/client.ts', '/app/style.css'] },
        }),
        tailwindcss(),
        build(),
      ],
    })
  6. Initialize the HonoX Server Entry File

    main

    A server entry file is required at app/server.ts. Use createApp() from honox/server to initialize your application. This file is called by Vite during development or build phases.

    // app/server.ts
    import { createApp } from 'honox/server'
    import { showRoutes } from 'hono/dev'
    
    const app = createApp()
    
    showRoutes(app)
    
    export default app
  7. Create a custom Not Found page

    main

    Implement a custom 404 handler by creating a _404.tsx file in your routes directory using the NotFoundHandler type from hono.

    // app/routes/_404.tsx
    import { NotFoundHandler } from 'hono'
    
    const handler: NotFoundHandler = (c) => {
      return c.render(<h1>Sorry, Not Found...</h1>)
    }
    
    export default handler
  8. Write Integration Tests with Vitest

    main

    Integration tests in HonoX use app.request() against a Hono instance created by createApp() to validate routing, middleware, and renderers. It is recommended to use Vitest for testing due to its close integration with Vite.

    Setup:

    1. Install Vitest: npm install -D vitest
    2. Run tests: npx vitest run

    If you use a vitest.config.ts file, use mergeConfig from vitest/config to combine it with your existing vite.config.ts to ensure settings are correctly inherited.

    // tests/integration/index.test.ts
    import { describe, expect, it } from 'vitest'
    import { createApp } from 'honox/server'
    
    const app = createApp()
    
    describe('Top page', () => {
      it("should return 'Hello, Hono!' when name query param is 'Hono'", async () => {
        const res = await app.request('/?name=Hono')
        const text = await res.text()
        expect(res.status).toBe(200)
        expect(res.headers.get('content-type')).toMatch(/text\/html/)
        expect(text).toMatch(/<h1[^>]*>\s*Hello, Hono!\s*<\/h1>/)
      })
    })
    // vitest.config.ts
    import { defineConfig, mergeConfig } from 'vitest/config'
    import viteConfig from './vite.config'
    
    export default mergeConfig(
      viteConfig,
      defineConfig({
        // Your Vitest-specific configuration here
      })
    )
  9. Use React with <Script />

    main

    If you have a dist/.vite/manifest.json exported, you can use the <Script /> component from honox/server in your renderer for easier asset management. Ensure your vite.config.ts is configured with manifest: true in the client build mode.

    // app/routes/_renderer.tsx
    import { reactRenderer } from '@hono/react-renderer'
    import { Script } from 'honox/server'
    
    export default reactRenderer(({ children, title }) => {
      return (
        <html lang='en'>
          <head>
            <meta charSet='UTF-8' />
            <meta name='viewport' content='width=device-width, initial-scale=1.0' />
            <Script src='/app/client.ts' async />
            {title ? <title>{title}</title> : ''}
          </head>
          <body>{children}</body>
        </html>
      )
    })
  10. Bring Your Own Renderer (React Example)

    main

    HonoX allows you to use UI libraries like React, Preact, or Solid as your renderer. To use React, follow these steps:

    1. Install dependencies:

      npm i @hono/react-renderer react react-dom hono
      npm i -D @types/react @types/react-dom
    2. Define Props in global.d.ts to ensure type safety for your renderer:

      import '@hono/react-renderer'
      declare module '@hono/react-renderer' {
        interface Props {
          title?: string
        }
      }
    3. Create the renderer in app/routes/_renderer.tsx using reactRenderer.

    4. Configure Client Hydration in app/client.ts using createClient from honox/client to map hydrate and createElement to your chosen library.

    5. Configure Vite in vite.config.ts to handle the client mode build (for assets) and the ssr mode (marking react and react-dom as external).

    6. Update tsconfig.json to set the jsxImportSource to react.

    // app/routes/_renderer.tsx
    import { reactRenderer } from '@hono/react-renderer'
    
    export default reactRenderer(({ children, title }) => {
      return (
        <html lang='en'>
          <head>
            <meta charSet='UTF-8' />
            <meta name='viewport' content='width=device-width, initial-scale=1.0' />
            {import.meta.env.PROD ? (
              <script type='module' src='/static/client.js'></script>
            ) : (
              <script type='module' src='/app/client.ts'></script>
            )}
            {title ? <title>{title}</title> : ''}
          </head>
          <body>{children}</body>
        </html>
      )
    })
  11. Setup Client-side Hydration with Islands

    main

    To enable client-side interactivity, use a renderer that loads a client entry file (e.g., app/client.ts) and uses the <Script /> component from honox/server.

    Client Entry File

    Create app/client.ts and call createClient():

    // app/client.ts
    import { createClient } from 'honox/client'
    
    createClient()

    Renderer with Script component

    Use the <Script /> component in your _renderer.tsx to load the client entry point:

    // app/routes/_renderer.tsx
    import { jsxRenderer } from 'hono/jsx-renderer'
    import { Script } from 'honox/server'
    
    export default jsxRenderer(({ children }) => {
      return (
        <html lang='en'>
          <head>
            <meta charset='UTF-8' />
            <meta name='viewport' content='width=device-width, initial-scale=1.0' />
            <Script src='/app/client.ts' />
          </head>
          <body>{children}</body>
        </html>
      )
    })
  12. Enable MDX Support

    main

    To use MDX, add @mdx-js/rollup to your vite.config.ts plugins. Ensure you configure the jsxImportSource to hono/jsx within the MDX plugin options.

    // vite.config.ts
    import devServer from '@hono/vite-dev-server'
    import mdx from '@mdx-js/rollup'
    import honox from 'honox/vite'
    import remarkFrontmatter from 'remark-frontmatter'
    import remarkMdxFrontmatter from 'remark-mdx-frontmatter'
    import { defineConfig } from 'vite'
    
    export default defineConfig(() => {
      return {
        plugins: [
          honox(),
          mdx({
            jsxImportSource: 'hono/jsx',
            remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter],
          }),
        ],
      }
    })