website
git clone https://git.pyrossh.dev/website
木 Personal website of pyrossh. Built with astrojs, shiki, vite.
11f8b8c
— pyrossh
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
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
89
|
+
### 1. Renderer (`_renderer.tsx`)
|
|
90
90
|
|
|
91
|
-
HonoX
|
|
91
|
+
Uses HonoX's standard `jsxRenderer` from `hono/jsx-renderer` — no custom BYOR bridge needed:
|
|
92
92
|
|
|
93
93
|
```typescript
|
|
94
|
-
// app/routes/_renderer.
|
|
94
|
+
// app/routes/_renderer.tsx
|
|
95
|
+
import { jsxRenderer } from 'hono/jsx-renderer'
|
|
95
|
-
import {
|
|
96
|
+
import { Script } from 'honox/server'
|
|
96
|
-
import {
|
|
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
|
|
101
|
+
export default jsxRenderer(({ children, title, description, css }) => {
|
|
99
|
-
c.setRender((
|
|
100
|
-
content: string,
|
|
101
|
-
head?: { title?: string; description?: string; css?: string }
|
|
102
|
-
) => {
|
|
103
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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": "
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
|
427
|
+
## What Stays Unchanged
|
|
409
428
|
|
|
410
429
|
- `packages/shared/config/` — site constants, repo list, tools list
|
|
411
|
-
- `packages/shared/ui/` — UI components (
|
|
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 |
|