website

#astro#js#html#css

git clone https://git.pyrossh.dev/website

木 Personal website of pyrossh. Built with astrojs, shiki, vite.


11f8b8cpyrossh 2026-07-08T19:14:11+05:30
docs: switch from typed-htmx to hono/jsx, simplify renderer
docs/superpowers/specs/{2026-07-08-migrate-to-honox-typed-htmx-design.md → 2026-07-08-migrate-to-honox-design.md} RENAMED
@@ -1,6 +1,6 @@
1
- # pyrossh.dev — Migrate to HonoX + typed-htmx
1
+ # pyrossh.dev — Migrate to HonoX
2
2
 
3
- **Goal:** Replace the current architecture of ~18 individual Cloudflare Workers + dev-router + assets worker with a single HonoX application using typed-htmx (via BYOR) and HTMX for interactivity. Keep the existing `packages/shared/` library packages unchanged.
3
+ **Goal:** Replace the current architecture of ~18 individual Cloudflare Workers + dev-router + assets worker with a single HonoX application using hono/jsx and HTMX for interactivity. Keep the existing `packages/shared/` library packages (with minor updates).
4
4
 
5
5
  ## Motivation
6
6
 
@@ -11,7 +11,7 @@ Current architecture is over-engineered for the site's complexity:
11
11
  - An assets worker that proxies non-CSS requests to an R2 bucket
12
12
  - Manual route pattern matching (`/^\/posts\/(.+)$/`) instead of standard routing
13
13
 
14
- A single HonoX app with file-based routing eliminates all of this while keeping the same typed-htmx components and HTMX interactivity.
14
+ A single HonoX app with file-based routing eliminates all of this. Using hono/jsx means no custom renderer bridge and no extra JSX runtime dependency.
15
15
 
16
16
  ## Architecture
17
17
 
@@ -54,7 +54,7 @@ pyrossh.dev/
54
54
  server.ts # createApp() entry point
55
55
  client.ts # Client entry (for HonoX, minimal)
56
56
  routes/
57
- _renderer.ts # typed-htmx renderer bridge
57
+ _renderer.tsx # Standard HonoX jsxRenderer
58
58
  _404.tsx # Not found
59
59
  _error.tsx # Error page
60
60
  index.tsx # Home (/)
@@ -86,60 +86,80 @@ pyrossh.dev/
86
86
 
87
87
  ## Key Components
88
88
 
89
- ### 1. Renderer Bridge (`_renderer.ts`)
89
+ ### 1. Renderer (`_renderer.tsx`)
90
90
 
91
- HonoX uses `c.render(content, head)` for page rendering. The renderer middleware wraps content in the site layout. Since we use typed-htmx (not hono/jsx), the renderer bridges the gap:
91
+ Uses HonoX's standard `jsxRenderer` from `hono/jsx-renderer` no custom BYOR bridge needed:
92
92
 
93
93
  ```typescript
94
- // app/routes/_renderer.ts
94
+ // app/routes/_renderer.tsx
95
+ import { jsxRenderer } from 'hono/jsx-renderer'
95
- import { createRoute } from 'honox/factory'
96
+ import { Script } from 'honox/server'
96
- import { Layout } from '@pyrossh/ui'
97
+ import { Header } from '@pyrossh/ui/header'
97
-
98
+ import { Footer } from '@pyrossh/ui/footer'
99
+ import { SITE_TITLE, SITE_DESCRIPTION, SITE_URL } from '@pyrossh/config'
100
+
98
- export default createRoute((c, next) => {
101
+ export default jsxRenderer(({ children, title, description, css }) => {
99
- c.setRender((
100
- content: string,
101
- head?: { title?: string; description?: string; css?: string }
102
- ) => {
103
- const html = Layout({
102
+ const url = new URL(c.req.url)
104
- title: head?.title ?? 'pyrossh',
105
- description: head?.description,
106
- css: head?.css,
107
- request: c.req.raw,
108
- children: content,
109
- })
110
- return c.html(html)
111
- })
112
- return next()
103
+ return (
104
+ <html lang="en">
105
+ <head>
106
+ <meta charset="utf-8" />
107
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
108
+ <meta name="theme-color" content="#131618" />
109
+ <link rel="icon" type="image/svg+xml" href="/assets/icons/icon.svg" />
110
+ <link rel="alternate" type="application/rss+xml" title={title} href="/rss.xml" />
111
+ <link rel="canonical" href={`${SITE_URL}${url.pathname}`} />
112
+ <link rel="stylesheet" href="/assets/css/shared.css" />
113
+ {css && <link rel="stylesheet" href={css} />}
114
+ <title>{title ?? SITE_TITLE}</title>
115
+ <meta name="title" content={title ?? SITE_TITLE} />
116
+ <meta name="description" content={description ?? SITE_DESCRIPTION} />
117
+ <meta name="author" content="pyrossh" />
118
+ <meta property="og:site_name" content="pyrossh.dev" />
119
+ <meta property="og:type" content="website" />
120
+ <meta property="og:url" content={url.href} />
121
+ <meta property="og:title" content={title ?? SITE_TITLE} />
122
+ <meta property="og:description" content={description ?? SITE_DESCRIPTION} />
123
+ <meta property="og:image" content="/assets/icons/icon.svg" />
124
+ <meta name="twitter:card" content="summary_large_image" />
125
+ <Script src="/app/client.ts" />
126
+ </head>
127
+ <body>
128
+ <Header currentPath={url.pathname} />
129
+ <div class="wrapper">
130
+ <main>{children}</main>
131
+ </div>
132
+ <Footer />
133
+ <script src="https://unpkg.com/[email protected]"></script>
134
+ </body>
135
+ </html>
136
+ )
113
137
  })
114
138
  ```
115
139
 
116
- The route handler renders its body content via typed-htmx JSX and passes it to `c.render()` with metadata. The renderer wraps it in the full HTML document via the existing `Layout` component from `@pyrossh/ui`.
117
-
118
- Note: typed-htmx JSX renders children as raw HTML (not escaped), so the string content from the route handler is safely embedded in the layout.
140
+ This is standard HonoX no custom `c.setRender()`, no type hacks. The Layout component is defined directly in the renderer (can be extracted later if needed). The `@pyrossh/ui/layout.tsx` Layout component is no longer needed since the renderer handles the full HTML document.
119
141
 
120
142
  ### 1a. TypeScript Configuration
121
143
 
122
- The `tsconfig.json` uses typed-htmx as the JSX import source (same as current workers):
144
+ Uses `hono/jsx` (built into Hono, no extra dependency):
123
145
 
124
146
  ```json
125
147
  {
126
148
  "compilerOptions": {
127
149
  "jsx": "react-jsx",
128
- "jsxImportSource": "typed-htmx/typed-html",
150
+ "jsxImportSource": "hono/jsx",
129
151
  "moduleResolution": "bundler",
130
152
  "target": "ESNext",
131
153
  "module": "ESNext",
132
154
  "strict": true
133
155
  },
134
- "include": ["app"]
156
+ "include": ["app", "packages/shared/ui"]
135
157
  }
136
158
  ```
137
159
 
138
- The HonoX framework files (`server.ts`, `vite.config.ts`) don't use JSX, so no conflict arises.
139
-
140
160
  ### 1b. HonoX Type Declarations (`global.d.ts`)
141
161
 
142
- The `global.d.ts` declares the `ContextRenderer` type so that `c.render()` accepts our typed-htmx content:
162
+ Minimal just the standard HonoX type setup:
143
163
 
144
164
  ```typescript
145
165
  import {} from 'hono'
@@ -159,18 +179,18 @@ declare module 'hono' {
159
179
 
160
180
  ### 2. Route Handler Pattern
161
181
 
162
- Each route handler follows a consistent pattern:
182
+ Each route handler follows a consistent pattern — same JSX syntax, different runtime:
163
183
 
164
184
  ```typescript
165
185
  // app/routes/index.tsx
166
186
  import { createRoute } from 'honox/factory'
167
- import { REPOS, TOOLS, SITE_TITLE } from '@pyrossh/config'
187
+ import { TOOLS, SITE_TITLE } from '@pyrossh/config'
168
188
 
169
189
  export default createRoute((c) => {
170
190
  return c.render(
171
191
  <div class="pageContainer">
172
192
  <h1 class="title">Hello!</h1>
173
- {/* page body content, same as current home worker */}
193
+ {/* page body content */}
174
194
  </div>,
175
195
  { title: SITE_TITLE, css: '/assets/css/workers/home.css' }
176
196
  )
@@ -189,7 +209,9 @@ export default createRoute(async (c) => {
189
209
  return c.render(
190
210
  <div>
191
211
  <h1>Posts</h1>
192
- {/* ... */}
212
+ {posts.map(post => (
213
+ <a href={`/posts/${post.id}`}>{post.data.title}</a>
214
+ ))}
193
215
  </div>,
194
216
  { title: 'Posts', css: '/assets/css/workers/posts-index.css' }
195
217
  )
@@ -198,14 +220,13 @@ export default createRoute(async (c) => {
198
220
 
199
221
  ### 2a. HTMX Form Handlers (POST/PUT)
200
222
 
201
- Workers that handle HTMX form submissions (e.g., issues CRUD) export named handlers alongside the default GET handler:
223
+ Same pattern named exports for POST/PUT alongside default GET:
202
224
 
203
225
  ```typescript
204
226
  // app/routes/repos/[id]/issues/[issueId].tsx
205
227
  import { createRoute } from 'honox/factory'
206
228
  import { getGitBugIssue, addGitBugIssueComment, getS3 } from '@pyrossh/core'
207
229
 
208
- // GET: Render page
209
230
  export default createRoute(async (c) => {
210
231
  const issueId = c.req.param('issueId')
211
232
  const issue = await getGitBugIssue(getS3(), issueId)
@@ -215,10 +236,8 @@ export default createRoute(async (c) => {
215
236
  )
216
237
  })
217
238
 
218
- // POST: HTMX form action (add comment)
219
239
  export const POST = createRoute(async (c) => {
220
240
  const formData = await c.req.parseBody()
221
- // handle comment creation
222
241
  return c.html(/* htmx fragment */)
223
242
  })
224
243
  ```
@@ -405,10 +424,10 @@ The existing `@pyrossh/ui` Layout component accepts a `request: Request` prop. I
405
424
  | `worker-configuration.d.ts` | Replaced by HonoX types |
406
425
  | `WEBSITE_BUCKET` R2 binding (recent addition) | No longer needed |
407
426
 
408
- ## What Stays Unchanged (mostly)
427
+ ## What Stays Unchanged
409
428
 
410
429
  - `packages/shared/config/` — site constants, repo list, tools list
411
- - `packages/shared/ui/` — UI components (Layout, Header, Footer, RepoLayout, etc.)
430
+ - `packages/shared/ui/` — UI components (Header, Footer, RepoLayout, etc.) with updated jsxImportSource
412
431
  - `assets/` directory — static files
413
432
  - `content/` directory — blog posts
414
433
 
@@ -423,6 +442,8 @@ The existing `@pyrossh/ui` Layout component accepts a `request: Request` prop. I
423
442
  | `packages/shared/core/src/gitReader.ts` | Replace `R2Bucket` with `S3mini` |
424
443
  | `packages/shared/core/src/content.ts` | Replace `R2Bucket` with `S3mini`, use `s3.listObjects/getObject` |
425
444
  | `packages/shared/core/src/repoContent.ts` | Replace `R2Bucket` with `S3mini`, use `s3.getObject` |
445
+ | `packages/shared/ui/` | Switch jsxImportSource from `typed-htmx/typed-html` to `hono/jsx`. JSX syntax is the same — no component rewrites needed. Remove `Layout` component (layout moved to `_renderer.tsx`). Remove `typed-htmx` dependency |
446
+ | `packages/shared/ui/tsconfig.json` | Change `jsxImportSource` to `hono/jsx` |
426
447
 
427
448
  ## Workspace Cleanup
428
449
 
@@ -450,6 +471,8 @@ The existing `@pyrossh/ui` Layout component accepts a `request: Request` prop. I
450
471
  }
451
472
  ```
452
473
 
474
+ `hono/jsx` is built into `hono` — no separate JSX dependency. `typed-htmx` is removed entirely. `hono/css` is also built into `hono` if needed for scoped styles later.
475
+
453
476
  ## Environment Variables
454
477
 
455
478
  | Variable | Required | Description |