llm-tips
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.
Viewing readonly version of main branch: v325View latest version
refactor static HTML to jsxRenderer
This guide shows how to migrate a static HTML file served by Hono into Hono’s jsxRenderer
middleware and apply it to dynamic tip pages such as /tips/youtube-transcript-to-notes
.
import { Hono } from 'hono'
import { serveStatic } from 'hono/static'
const app = new Hono()
app.use('/', serveStatic({ root: './public', spa: false }))
app.listen({ port: 3000 })
public/index.html
contains your HTML template.
npm install hono jsx-renderer
-
Create
src/Layout.tsx
:import React from 'react' export function Layout({ children, title }: { children: React.ReactNode; title?: string }) { return ( <html lang="en"> <head> <meta charSet="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>{title || 'My App'}</title> <link rel="stylesheet" href="/styles.css" /> </head> <body> <header> <nav><a href="/">Home</a> | <a href="/tips">Tips</a></nav> </header> <main id="root">{children}</main> <script src="/bundle.js"></script> </body> </html> ) } -
Move static assets (
styles.css
,bundle.js
, images) intopublic/
.
import { Hono } from 'hono'
import { jsxRenderer } from 'hono/jsx-renderer'
import { Layout } from './Layout'
const app = new Hono()
// Apply renderer for all GET requests
app.use('GET', '*', jsxRenderer(Layout))
-
Home route:
app.get('/', (c) => { return c.render( <> <h1>Welcome to My App</h1> <p>This replaces index.html</p> </> ) }) -
Tips list:
import tips from './data/tips.json' // array of { slug, title } app.get('/tips', (c) => { return c.render( <ul> {tips.map((tip) => ( <li key={tip.slug}> <a href={`/tips/${tip.slug}`}>{tip.title}</a> </li> ))} </ul> ) }) -
Dynamic tip page:
import fs from 'fs' import path from 'path' app.get('/tips/:slug', async (c) => { const { slug } = c.req.param() const file = path.join(process.cwd(), 'tips', `${slug}.html`) const content = fs.readFileSync(file, 'utf-8') return c.render(<div dangerouslySetInnerHTML={{ __html: content }} />, { title: slug.replace('-', ' ') }) })
npm run dev
- Visit
http://localhost:3000/
for home. - Visit
http://localhost:3000/tips
for list of tips. - Visit
http://localhost:3000/tips/youtube-transcript-to-notes
to see a rendered tip.
Next steps?
- Nested layouts (e.g. tutorial pages under
/tips
)- Streaming rendering for large content
- Context‑aware UI (
useRequestContext
) for breadcrumbs and metadata