Public
Isento property ops: staff/owner/guest portals + fee ledger
isentoopsproperty-management
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.

hono-jsx-starter

A minimal Hono + Hono JSX starter for Val Town. Server-rendered HTML, no build step, no React.

Picking a starter

StarterWhen to pick it
basic-html-starterStatic HTML pages. Sprinkle of vanilla JS. No server-side rendering.
hono-jsx-starter (you're here)Dynamic server-rendered HTML with JSX. API routes. No React, no SPA.
react-hono-starterClient-side React SPA with a Hono API backend.

What's here

index.tsx          Hono app, Layout, routes
public/
  favicon.svg      Static asset
  counter.ts       Tiny client-side script

The whole app fits in one file. As you grow it, the natural next moves are:

  • Pull Layout into its own .tsx file once you have 3+ pages.
  • Split routes with app.route("/api", api) if /api/* grows.
  • Add more files under public/ — they're served by a single app.get("/public/*", ...) route.

Patterns worth knowing

JSX pragma. Every .tsx file that uses JSX needs this at the top:

/** @jsxImportSource npm:hono@4/jsx */

Server state. The counter on / is let count = 0 at module scope. It lives in memory on the Val Town server and survives across requests — but resets whenever the val restarts (deploys, idle timeouts, etc.). For persistent state, swap it for std/sqlite or std/blob.

API routes. The Increment button POSTs to /api/increment, which returns JSON. Mix server-rendered pages and JSON endpoints in the same Hono app:

app.post("/api/increment", (c) => c.json({ count: ++count }));

Static files. serveFile reads the file by path and serves it with the right content-type. .ts files are transpiled to JS on request by Val Town — so you can write public/counter.ts and reference it as <script type="module" src="/public/counter.ts">. (You'll see an Open in Val Town: ... comment at the top of the served JS — that's the transpiler.)

app.get("/public/*", (c) => serveFile(c.req.path));

Client-side JS. Drop a .ts file in public/, reference it from your JSX with a <script type="module">. No bundler, no build step.

View source. app.get("/source", c => c.redirect(parseVal().links.self.val)) gives you a tidy link from your live app back to the code on Val Town.

Error handling. app.onError((err) => Promise.reject(err)) unwraps Hono's Internal Server Error and rethrows the original — so Val Town's logs show the real stack trace.

Try it

This is a Val Town HTTP val — just open it on val.town and hit the endpoint. No npm install required. Click the Remix button to make it your own.