├── public
└── .assetsignore
├── src
├── style.css
├── client
│ ├── main.tsx
│ ├── App.tsx
│ └── Chat.tsx
└── server
│ ├── index.tsx
│ ├── renderer.tsx
│ └── api.ts
├── wrangler.jsonc
├── tsconfig.client.json
├── tsconfig.server.json
├── vite.config.ts
├── tsconfig.json
├── .gitignore
└── package.json
/public/.assetsignore:
--------------------------------------------------------------------------------
1 | .vite
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-family: Arial, Helvetica, sans-serif;
3 | }
4 |
--------------------------------------------------------------------------------
/src/client/main.tsx:
--------------------------------------------------------------------------------
1 | import { createRoot } from 'react-dom/client'
2 | import App from './App'
3 |
4 | createRoot(document.getElementById('root')!).render()
5 |
--------------------------------------------------------------------------------
/src/client/App.tsx:
--------------------------------------------------------------------------------
1 | import Chat from './Chat'
2 |
3 | export default function App() {
4 | return (
5 | <>
6 |
Chatbot
7 |
8 | >
9 | )
10 | }
11 |
--------------------------------------------------------------------------------
/wrangler.jsonc:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "node_modules/wrangler/config-schema.json",
3 | "name": "ai-sdk-with-hono",
4 | "compatibility_date": "2025-08-03",
5 | "main": "./src/server/index.tsx"
6 | }
--------------------------------------------------------------------------------
/tsconfig.client.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "composite": true,
5 | "lib": [
6 | "ESNext",
7 | "DOM"
8 | ]
9 | },
10 | "include": [
11 | "src/client/**/*.tsx",
12 | "src/client/**/*.ts"
13 | ]
14 | }
--------------------------------------------------------------------------------
/tsconfig.server.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "composite": true,
5 | "lib": [
6 | "ESNext"
7 | ],
8 | },
9 | "include": [
10 | "src/server/**/*.tsx",
11 | "src/server/**/*.ts",
12 | "worker-configuration.d.ts"
13 | ]
14 | }
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { cloudflare } from '@cloudflare/vite-plugin'
2 | import { defineConfig } from 'vite'
3 | import ssrPlugin from 'vite-ssr-components/plugin'
4 |
5 | export default defineConfig({
6 | build: {
7 | minify: true
8 | },
9 | plugins: [cloudflare(), ssrPlugin()]
10 | })
11 |
--------------------------------------------------------------------------------
/src/server/index.tsx:
--------------------------------------------------------------------------------
1 | import { Hono } from 'hono'
2 | import { renderer } from './renderer'
3 | import apiApp from './api'
4 |
5 | const app = new Hono()
6 |
7 | app.use(renderer)
8 |
9 | app.get('/', (c) => {
10 | return c.render()
11 | })
12 |
13 | app.route('/api', apiApp)
14 |
15 | export default app
16 |
--------------------------------------------------------------------------------
/src/server/renderer.tsx:
--------------------------------------------------------------------------------
1 | import { reactRenderer } from '@hono/react-renderer'
2 | import { Link, Script, ViteClient } from 'vite-ssr-components/react'
3 |
4 | export const renderer = reactRenderer(({ children }) => {
5 | return (
6 |
7 |
8 |
9 |
10 |
11 |
12 | {children}
13 |
14 | )
15 | })
16 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "ESNext",
5 | "moduleResolution": "Bundler",
6 | "strict": true,
7 | "skipLibCheck": true,
8 | "types": [
9 | "vite/client"
10 | ],
11 | "jsx": "react-jsx",
12 | "jsxImportSource": "react"
13 | },
14 | "references": [
15 | {
16 | "path": "./tsconfig.client.json"
17 | },
18 | {
19 | "path": "./tsconfig.server.json"
20 | }
21 | ],
22 | "include": []
23 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bun.lock
2 | package-lock.json
3 | yarn.lock
4 |
5 | worker-configuration.d.ts
6 |
7 | # prod
8 | dist/
9 |
10 | # dev
11 | .yarn/
12 | !.yarn/releases
13 | .vscode/*
14 | !.vscode/launch.json
15 | !.vscode/*.code-snippets
16 | .idea/workspace.xml
17 | .idea/usage.statistics.xml
18 | .idea/shelf
19 |
20 | # deps
21 | node_modules/
22 | .wrangler
23 |
24 | # env
25 | .env
26 | .env.production
27 | .dev.vars
28 |
29 | # logs
30 | logs/
31 | *.log
32 | npm-debug.log*
33 | yarn-debug.log*
34 | yarn-error.log*
35 | pnpm-debug.log*
36 | lerna-debug.log*
37 |
38 | # misc
39 | .DS_Store
40 |
--------------------------------------------------------------------------------
/src/server/api.ts:
--------------------------------------------------------------------------------
1 | import { Hono } from 'hono'
2 | import { createAnthropic } from '@ai-sdk/anthropic'
3 | import { convertToModelMessages, streamText, UIMessage } from 'ai'
4 |
5 | const app = new Hono<{
6 | Bindings: Cloudflare.Env
7 | }>()
8 |
9 | app.post('/chat', async (c) => {
10 | const { messages } = await c.req.json<{ messages: UIMessage[] }>()
11 |
12 | const anthropic = createAnthropic({
13 | apiKey: c.env.ANTHROPIC_API_KEY
14 | })
15 |
16 | const result = streamText({
17 | model: anthropic('claude-sonnet-4-20250514'),
18 | messages: convertToModelMessages(messages)
19 | })
20 |
21 | return result.toUIMessageStreamResponse()
22 | })
23 |
24 | export default app
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ai-sdk-with-hono",
3 | "type": "module",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "vite build",
7 | "preview": "$npm_execpath run build && vite preview",
8 | "deploy": "$npm_execpath run build && wrangler deploy",
9 | "cf-typegen": "wrangler types --env-interface CloudflareBindings"
10 | },
11 | "dependencies": {
12 | "@ai-sdk/anthropic": "^2.0.8",
13 | "@ai-sdk/react": "^2.0.25",
14 | "@hono/react-renderer": "^1.0.1",
15 | "ai": "^5.0.25",
16 | "hono": "^4.9.4",
17 | "react": "^19.1.1",
18 | "react-dom": "^19.1.1",
19 | "zod": "^4.1.3"
20 | },
21 | "devDependencies": {
22 | "@cloudflare/vite-plugin": "^1.2.3",
23 | "@types/react": "^19.1.11",
24 | "@types/react-dom": "^19.1.7",
25 | "vite": "^6.3.5",
26 | "vite-ssr-components": "^0.5.1",
27 | "wrangler": "^4.17.0"
28 | }
29 | }
--------------------------------------------------------------------------------
/src/client/Chat.tsx:
--------------------------------------------------------------------------------
1 | import { useChat } from '@ai-sdk/react'
2 | import { useState } from 'react'
3 |
4 | export default function Chat() {
5 | const { messages, sendMessage, status } = useChat()
6 | const [input, setInput] = useState('')
7 |
8 | return (
9 | <>
10 | {messages.map((message) => (
11 |
12 | {message.role === 'user' ? 'User: ' : 'AI: '}
13 | {message.parts.map((part, index) => (part.type === 'text' ? {part.text} : null))}
14 |
15 | ))}
16 |
17 |
36 | >
37 | )
38 | }
39 |
--------------------------------------------------------------------------------