Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

HTML with JSX

Besides JSON, Hono can serve HTML pages. Writing markup as strings is awkward and unsafe, so Hono has built-in JSX — a syntax where HTML tags are written right inside TypeScript code. This is not React: Hono's JSX just turns markup into an HTML string on the server, with no code in the browser.

Setup

Files with JSX must have the .tsx extension. The project's tsconfig.json already has the lines you need; check them:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "hono/jsx"
  }
}

The second line tells TypeScript to take JSX from Hono rather than React. Nothing needs to be installed.

The first page

Create src/routes/pages.tsx:

// src/routes/pages.tsx
import { Hono } from 'hono'
 
const pages = new Hono()
 
pages.get('/', (c) => {
  return c.html(<h1>A blog about programming</h1>)
})
 
export default pages

Mount the group in src/index.ts with app.route('/', pages) and the import import pages from './routes/pages.js'. Open http://localhost:3000/ and the heading appears. c.html() accepts JSX and sets the Content-Type: text/html header itself.

A component

A component is a function that returns markup. Its name starts with a capital letter, and it is used like a tag:

// src/components/PostCard.tsx
import type { FC } from 'hono/jsx'
 
type Props = {
  title: string
  body: string
}
 
export const PostCard: FC<Props> = ({ title, body }) => {
  return (
    <article>
      <h2>{title}</h2>
      <p>{body}</p>
    </article>
  )
}

Data reaches the component through props — an object with the attributes given where the tag is used. Values in curly braces are escaped: if a title contains a script tag, it is printed as text and does not run.

Using the component in a list

Show all posts on the /posts page:

// src/routes/pages.tsx
import { PostCard } from '../components/PostCard.js'
 
const posts = [
  { id: 1, title: 'First post', body: 'Hello, world!' },
  { id: 2, title: 'Second post', body: 'Learning Hono.' },
]
 
pages.get('/posts', (c) => {
  return c.html(
    <main>
      <h1>Posts</h1>
      {posts.map((post) => (
        <PostCard title={post.title} body={post.body} />
      ))}
    </main>,
  )
})

The page http://localhost:3000/posts shows a heading and two post cards. The list is built with an ordinary map(). React-style key attributes are not needed here: the page is assembled once on the server.

Differences from HTML

HTMLJSX
class="post"class="post" — in Hono you can write it as in HTML
<input><input /> — a single tag is closed
text with dataan expression in curly braces
onclick="..."event handlers do not work on the server

Common mistake

Writing JSX in a .ts file. TypeScript does not parse markup in such files and reports lots of confusing errors. Rename the file to .tsx, and keep writing .js in imports: ../components/PostCard.js.

Thanks for reading!

Donate to the site — we work hard for you.