Socket
Socket
Sign inDemoInstall

honox

Package Overview
Dependencies
69
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

    honox

**HonoX** is a simple and fast meta framework for creating websites and Web APIs with Server-Side Rendering - (formerly _[Sonik](https://github.com/sonikjs/sonik)_). It stands on the shoulders of giants; built on [Hono](https://hono.dev/), [Vite](https://


Version published
Maintainers
1
Created

Readme

Source

HonoX

HonoX is a simple and fast meta framework for creating websites and Web APIs with Server-Side Rendering - (formerly Sonik). It stands on the shoulders of giants; built on Hono, Vite, and UI libraries.

Note: HonoX is currently in a "beta stage". There will be breaking changes without any announcement. Don't use it in production. However, feel free to try it in your hobby project and give us your feedback!

Features

  • File-based routing - You can create a large app by separating concerns.
  • Fast SSR - Rendering is ultra-fast thanks to Hono.
  • BYOR - You can bring your own renderer, not only one using hono/jsx.
  • Island hydration - If you want interactions, create an island. JavaScript is hydrated only for it.
  • Middleware - It works as Hono, so you can use a lot of Hono's middleware.

Get Started - Basic

Let's create a basic HonoX application using hono/jsx as a renderer. This application has no client JavaScript and renders JSX on the server side.

Project Structure

Below is a typical project structure for a HonoX application.

.
├── app
│   ├── global.d.ts // global type definitions
│   ├── routes
│   │   ├── _404.tsx // not found page
│   │   ├── _error.tsx // error page
│   │   ├── _renderer.tsx // renderer definition
│   │   ├── about
│   │   │   └── [name].tsx // matches `/about/:name`
│   │   └── index.tsx // matches `/`
│   └── server.ts // server entry file
├── package.json
├── tsconfig.json
└── vite.config.ts

vite.config.ts

The minimum Vite setup for development is as follows:

import { defineConfig } from 'vite'
import honox from 'honox/vite'

export default defineConfig({
  plugins: [honox()],
})

Server Entry File

A server entry file is required. The file is should be placed at app/server.ts. This file is first called by the Vite during the development or build phase.

In the entry file, simply initialize your app using the createApp() function. app will be an instance of Hono, so you can use Hono's middleware and the showRoutes()inhono/dev.

// app/server.ts
import { createApp } from 'honox/server'
import { showRoutes } from 'hono/dev'

const app = createApp()

showRoutes(app)

export default app

Routes

There are three ways to define routes.

1. createRoute()

Each route should return an array of Handler | MiddlewareHandler. createRoute() is a helper function to return it. You can write a route for a GET request with default export.

// `createRoute()` helps you create handlers
import { createRoute } from 'honox/factory'

export default createRoute((c) => {
  return c.render(
    <div>
      <h1>Hello!</h1>
    </div>
  )
})

You can also handle methods other than GET by export POST, PUT, and DELETE.

import { createRoute } from 'honox/factory'
import { getCookie, setCookie } from 'hono/cookie'

export const POST = createRoute(async (c) => {
  const { name } = await c.req.parseBody<{ name: string }>()
  setCookie(c, 'name', name)
  return c.redirect('/')
})

export default createRoute((c) => {
  const name = getCookie(c, 'name') ?? 'no name'
  return c.render(
    <div>
      <h1>Hello, {name}!</h1>
      <form method='POST'>
        <input type='text' name='name' placeholder='name' />
        <input type='submit' />
      </form>
    </div>
  )
})
2. Using Hono instance

You can create API endpoints by exporting an instance of the Hono object.

// app/routes/about/index.ts
import { Hono } from 'hono'

const app = new Hono()

// matches `/about/:name`
app.get('/:name', (c) => {
  const name = c.req.param('name')
  return c.json({
    'your name is': name,
  })
})

export default app
3. Just return JSX

Or simply, you can just return JSX.

export default function Home() {
  return <h1>Welcome!</h1>
}

Renderer

Define your renderer - the middleware that does c.setRender() - by writing it in _renderer.tsx.

Before writing _renderer.tsx, write the Renderer type definition in global.d.ts.

// app/global.d.ts
import 'hono'

type Head = {
  title?: string
}

declare module 'hono' {
  interface ContextRenderer {
    (content: string | Promise<string>, head?: Head): Response | Promise<Response>
  }
}

The JSX Renderer middleware allows you to create a Renderer as follows:

// app/routes/_renderer.tsx
import { jsxRenderer } from 'hono/jsx-renderer'

export default jsxRenderer(({ children, title }) => {
  return (
    <html lang='en'>
      <head>
        <meta charset='UTF-8' />
        <meta name='viewport' content='width=device-width, initial-scale=1.0' />
        {title ? <title>{title}</title> : ''}
      </head>
      <body>{children}</body>
    </html>
  )
})

The _renderer.tsx is applied under each directory, and the app/routes/posts/_renderer.tsx is applied in app/routes/posts/*.

Not Found page

You can write a custom Not Found page in _404.tx.

// app/routes/_404.tsx
import { NotFoundHandler } from 'hono'

const handler: NotFoundHandler = (c) => {
  return c.render(<h1>Sorry, Not Found...</h1>)
}

export default handler

Error Page

You can write a custom Error page in _error.tx.

// app/routes/_error.ts
import { ErrorHandler } from 'hono'

const handler: ErrorHandler = (e, c) => {
  return c.render(<h1>Error! {e.message}</h1>)
}

export default handler

Get Started - with Client

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

Project Structure

The 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 can also load the JavaScript file for the production according to the variable import.meta.env.PROD.

// app/routes/_renderer.tsx
import { jsxRenderer } from 'hono/jsx-renderer'

export default jsxRenderer(({ children }) => {
  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>
        )}
      </head>
      <body>{children}</body>
    </html>
  )
})

Client Entry File

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

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

createClient()

Interactions

Function components placed in app/islands/* are also sent to the client side. For example, you can write interactive component such as the following counter:

// app/islands/counter.tsx
import { useState } from 'hono/jsx'

export default function Counter() {
  const [count, setCount] = useState(0)
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  )
}

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

// app/routes/index.tsx
import { createRoute } from 'honox/factory'
import Counter from '../islands/counter'

export default createRoute((c) => {
  return c.render(
    <div>
      <h1>Hello</h1>
      <Counter />
    </div>
  )
})

BYOR - Bring Your Own Renderer

You can bring your own renderer using a UI library like React, Preact, Solid, or others.

Note: We cannot provide technical support for the renderer you bring.

React case

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.

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

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

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

// 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>
  )
})

The app/client.ts will be like this.

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

createClient({
  hydrate: async (elem, root) => {
    const { hydrateRoot } = await import('react-dom/client')
    hydrateRoot(root, elem)
  },
  createElement: async (type: any, props: any, ...children: any[]) => {
    const { createElement } = await import('react')
    return createElement(type, props, ...children)
  },
})

Guides

Using Middleware

You can use Hono's Middleware in each root file with the same syntax as Hono. For example, to validate a value with the Zod Validator, do the following:

import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'

const schema = z.object({
  name: z.string().max(10),
})

export const POST = createRoute(zValidator('form', schema), async (c) => {
  const { name } = c.req.valid('form')
  setCookie(c, 'name', name)
  return c.redirect('/')
})

Using Tailwind CSS

Given that HonoX is Vite-centric, if you wish to utilize Tailwind CSS, simply adhere to the official instructions.

Prepare tailwind.config.js and postcss.config.js:

// tailwind.config.js
export default {
  content: ['./app/**/*.tsx'],
  theme: {
    extend: {},
  },
  plugins: [],
}
// postcss.config.js
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}

Write app/style.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

Finally, import it in a renderer file:

// app/routes/_renderer.tsx
import { jsxRenderer } from 'hono/jsx-renderer'

export default jsxRenderer(({ children }) => {
  return (
    <html lang='en'>
      <head>
        <meta charset='UTF-8' />
        <meta name='viewport' content='width=device-width, initial-scale=1.0' />
        {import.meta.env.PROD ? (
          <link href='static/assets/style.css' rel='stylesheet' />
        ) : (
          <link href='/app/style.css' rel='stylesheet' />
        )}
      </head>
      <body>{children}</body>
    </html>
  )
})

MDX

MDX can also be used. Here is the 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 '../../node_modules/vite'

const entry = './app/server.ts'

export default defineConfig(() => {
  return {
    plugins: [
      honox(),
      devServer({ entry }),
      mdx({
        jsxImportSource: 'hono/jsx',
        remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter],
      }),
    ],
  }
})

Blog site can be created.

// app/routes/index.tsx
import type { Meta } from '../types'

export default function Top() {
  const posts = import.meta.glob<{ frontmatter: Meta }>('./posts/*.mdx', {
    eager: true,
  })
  return (
    <div>
      <h2>Posts</h2>
      <ul class='article-list'>
        {Object.entries(posts).map(([id, module]) => {
          if (module.frontmatter) {
            return (
              <li>
                <a href={`${id.replace(/\.mdx$/, '')}`}>{module.frontmatter.title}</a>
              </li>
            )
          }
        })}
      </ul>
    </div>
  )
}

Deployment

Since a HonoX instance is essentially a Hono instance, it can be deployed on any platform that Hono supports.

Cloudflare Pages

Setup the vite.config.ts:

import { defineConfig } from 'vite'
import honox from 'honox/vite'
import pages from '@hono/vite-cloudflare-pages'

export default defineConfig({
  plugins: [honox(), pages()],
})

If you want to include client side scripts and assets:

// vite.config.ts
import { defineConfig } from 'vite'
import honox from 'honox/vite'
import pages from '@hono/vite-cloudflare-pages'

export default defineConfig(({ mode }) => {
  if (mode === 'client') {
    return {
      build: {
        rollupOptions: {
          input: ['./app/client.ts'],
          output: {
            entryFileNames: 'static/client.js',
            chunkFileNames: 'static/assets/[name]-[hash].js',
            assetFileNames: 'static/assets/[name].[ext]',
          },
        },
        emptyOutDir: false,
        copyPublicDir: false,
      },
    }
  } else {
    return {
      plugins: [honox(), pages()],
    }
  }
})

Build command (including a client):

vite build && vite build --mode client

Deploy with the following commands after build. Ensure you have Wrangler installed:

wrangler pages deploy ./dist

SSG - Static Site Generation

Using Hono's SSG feature, you can generate static HTML for each route.

import { defineConfig } from 'vite'
import honox from 'honox/vite'
import ssg from '@hono/vite-ssg'

const entry = './app/server.ts'

export default defineConfig(() => {
  return {
    plugins: [honox(), devServer({ entry }), ssg({ entry })],
  }
})

You can also deploy it to Cloudflare Pages.

wrangler pages deploy ./dist

Examples

Authors

License

MIT

FAQs

Last updated on 28 Jan 2024

Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc