Repository: honojs/honox (ts)

Issue:
Using the response to answer to request if it is returned by JSX routing
### What is the feature you are proposing?

Sometimes I need to redirect users to another page. If I have the JSX route I should to change it and use `c.render` and `createRouter` to do it. I think the simplest way here is check the result of the route function.


```typescript
import { Context } from "hono";

export default async function About(c: Context){
  if(someCondition){
    return c.redirect('/');
  }

  return <h1>About</h1>
}
```
But now the response is converted to `string` and we have what we have

<img width="1624" alt="Image" src="https://github.com/user-attachments/assets/48f11993-f8f5-44b4-b876-f579f1bae36b" />

If you can support this behavior I will be the happiest man in the world! If you have no capacity for it, but this feature can land in honox, I can try to implement it by meself


Pull request:
feat: add support for `Response` in component routing
Close #263

Patch:
diff --git a/mocks/app-function/routes/api-response.tsx b/mocks/app-function/routes/api-response.tsx
new file mode 100644
index 0000000..4f370bd
--- /dev/null
+++ b/mocks/app-function/routes/api-response.tsx
@@ -0,0 +1,5 @@
+export default function ApiResponse() {
+  return new Response(JSON.stringify({ message: 'API Response' }), {
+    headers: { 'Content-Type': 'application/json' },
+  })
+}
diff --git a/mocks/app-function/routes/async-jsx.tsx b/mocks/app-function/routes/async-jsx.tsx
new file mode 100644
index 0000000..8e1a109
--- /dev/null
+++ b/mocks/app-function/routes/async-jsx.tsx
@@ -0,0 +1,4 @@
+export default async function AsyncJsx() {
+  await new Promise((resolve) => setTimeout(resolve, 10)) // simulate async work
+  return <div>Async JSX Response</div>
+}
diff --git a/mocks/app-function/routes/async-response.tsx b/mocks/app-function/routes/async-response.tsx
new file mode 100644
index 0000000..a5b1fab
--- /dev/null
+++ b/mocks/app-function/routes/async-response.tsx
@@ -0,0 +1,9 @@
+export default async function AsyncResponse() {
+  return new Response(JSON.stringify({ message: 'Async Response' }), {
+    status: 201,
+    headers: {
+      'Content-Type': 'application/json',
+      'x-custom': 'async',
+    },
+  })
+}
diff --git a/mocks/app-function/routes/jsx-response.tsx b/mocks/app-function/routes/jsx-response.tsx
new file mode 100644
index 0000000..3051372
--- /dev/null
+++ b/mocks/app-function/routes/jsx-response.tsx
@@ -0,0 +1,3 @@
+export default function JsxResponse() {
+  return <div>JSX Response</div>
+}
diff --git a/src/server/server.ts b/src/server/server.ts
index 465592e..5636a22 100644
--- a/src/server/server.ts
+++ b/src/server/server.ts
@@ -182,8 +182,14 @@ export const createApp = <E extends Env>(options: BaseServerOptions<E>): Hono<E>
         // export default function Helle() {}
         if (typeof routeDefault === 'function') {
           subApp.get(path, setInnerMeta)
-          subApp.get(path, (c) => {
-            return c.render(routeDefault(c), route as any)
+          subApp.get(path, async (c) => {
+            const result = await routeDefault(c)
+
+            if (result instanceof Response) {
+              return result
+            }
+
+            return c.render(result, route as any)
           })
         }
       }


Test patch:
diff --git a/test-integration/apps.test.ts b/test-integration/apps.test.ts
index 570ce83..306f988 100644
--- a/test-integration/apps.test.ts
+++ b/test-integration/apps.test.ts
@@ -877,3 +877,39 @@ describe('Nested Dynamic Routes', () => {
     expect(await res.text()).toBe('<b>Resource2 Id abcdef / 12345</b>')
   })
 })
+
+describe('Function Component Response', () => {
+  const ROUTES = import.meta.glob('../mocks/app-function/routes/**/[a-z[-][a-z[_-]*.(tsx|ts)', {
+    eager: true,
+  })
+
+  const app = createApp({
+    root: '../mocks/app-function/routes',
+    ROUTES: ROUTES as any,
+  })
+
+  it('Should handle direct Response return from function component', async () => {
+    const res = await app.request('/api-response')
+    expect(res.status).toBe(200)
+    expect(await res.json()).toEqual({ message: 'API Response' })
+  })
+
+  it('Should handle JSX return from function component', async () => {
+    const res = await app.request('/jsx-response')
+    expect(res.status).toBe(200)
+    expect(await res.text()).toBe('<div>JSX Response</div>')
+  })
+
+  it('Should handle async function component with Response', async () => {
+    const res = await app.request('/async-response')
+    expect(res.status).toBe(201)
+    expect(res.headers.get('x-custom')).toBe('async')
+    expect(await res.json()).toEqual({ message: 'Async Response' })
+  })
+
+  it('Should handle async function component with JSX', async () => {
+    const res = await app.request('/async-jsx')
+    expect(res.status).toBe(200)
+    expect(await res.text()).toBe('<div>Async JSX Response</div>')
+  })
+})