🔥

Cloudflareに入門する

2025/02/01に公開

CloudflareでHonoを試してみる

Cloudflareをはじめて使用します。Honoをデプロイしてみます。

Cloudflareのアカウントを作成が前提です。また、devcontainer環境でWrangler loginを行う方法を参考にしています。

Hono

pnpm add hono
pnpm add -D wrangler

ディレクトリ構成

.
├── package.json
├── src
│   └── index.ts
├── tsconfig.json
└── wrangler.json

 index.ts

import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => {
  return c.json({ message: 'Hono🔥' })
})

export default app

 package.json

{
  "name": "@apps/hono",
  "scripts": {
    "dev": "wrangler dev",
    "preview": "wrangler dev server-build/index.js",
    "login": "wrangler login",
    "logout": "wrangler logout",
    "delete": "wrangler delete",
    "deploy": "wrangler deploy --minify"
  },
  "dependencies": {
    "hono": "^4.6.20"
  },
  "devDependencies": {
    "wrangler": "^3.107.2"
  }
}

 wrangler.json

{
  "name": "***",
  "main": "src/index.ts",
  "compatibility_date": "****-**-**",
  "dev": {
    "ip": "0.0.0.0",
    "port": 8787
  }
}

 ログイン

pnpm wrangler login

 デプロイ

pnpm run deploy

 ログアウト

pnpm wrangler logout

React Hono

pnpm add react react-dom hono @hono/zod-openapi
pnpm add -D @types/react @types/react-dom vite @hono/vite-dev-server @hono/swagger-ui wrangler cloudflare-workers

ディレクトリ構成

.
├── package.json
├── src
│   ├── app.tsx
│   ├── index.tsx
│   └── main.tsx
├── tsconfig.json
├── vite.config.ts
└── wrangler.toml

 wrangler.toml

name = "***"
compatibility_date = "****-**-**"
assets = { directory = "./dist/", not_found_handling = "none" }

[dev]
ip = "0.0.0.0"
port = 8787

 package.json

{
  "name": "@apps/hono_react",
  "type": "module",
  "scripts": {
    "dev": "vite --host",
    "build": "vite build --mode client && vite build",
    "preview": "wrangler dev server-build/index.js",
    "login": "wrangler login",
    "logout": "wrangler logout",
    "delete": "wrangler delete",
    "deploy": "pnpm build && wrangler deploy server-build/index.js"
  },

  "dependencies": {
    "@hono/zod-openapi": "^0.18.3",
    "hono": "^4.6.20",
    "react": "^19.0.0",
    "react-dom": "^19.0.0"
  },
  "devDependencies": {
    "@hono/swagger-ui": "^0.5.0",
    "@hono/vite-build": "^1.3.0",
    "@hono/vite-dev-server": "^0.18.1",
    "@types/react": "^19.0.8",
    "@types/react-dom": "^19.0.3",
    "cloudflare-workers": "link:@hono/vite-build/cloudflare-workers",
    "vite": "^6.0.11",
    "wrangler": "^3.107.2"
  }
}

 app.tsx

import { useState } from 'react'
import { client } from './'

const App = () => {
  const [message, setMessage] = useState('')

  const onSubmit = async () => {
    const res = await client.$get()
    const data = await res.json()
    setMessage(data.message)
  }

  return (
    <>
      <h1>Hono🔥 React</h1>
      <button type='button' onClick={onSubmit}>
        Get Message
      </button>
      <h1>{message}</h1>
    </>
  )
}

export default App

 index.tsx

import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi'
import type { RouteHandler } from '@hono/zod-openapi'
import { hc } from 'hono/client'
import { swaggerUI } from '@hono/swagger-ui'
import { renderToString } from 'react-dom/server'

const app = new OpenAPIHono()

const get = createRoute({
  tags: ['Hono'],
  method: 'get',
  path: '/',
  description: 'Hono🔥 React',
  responses: {
    200: {
      description: 'Hono🔥',
      content: {
        'application/json': {
          schema: z.object({
            message: z.string(),
          }),
        },
      },
    },
  },
})

export const getHandler: RouteHandler<typeof get> = async (c) => {
  return c.json({ message: 'Hono🔥 React' })
}

export const api = app.basePath('/api').openapi(get, getHandler)

// Swagger
api
  .doc('/doc', {
    info: {
      title: 'Hono API',
      version: 'v1',
    },
    openapi: '3.1.0',
    tags: [
      {
        name: 'Hono',
        description: 'Hono API',
      },
    ],
  })
  .get('/ui', swaggerUI({ url: '/api/doc' }))

type AddType = typeof api

export const client = hc<AddType>('/').api

app.get('*', (c) => {
  return c.html(
    renderToString(
      <html lang='en'>
        <head>
          <meta charSet='utf-8' />
          <meta content='width=device-width, initial-scale=1' name='viewport' />
          <title>Hono🔥 React</title>
          {import.meta.env.PROD ? (
            <script type='module' src='/static/main.js'></script>
          ) : (
            <script type='module' src='/src/main.tsx'></script>
          )}
        </head>
        <body>
          <div id='root' />
        </body>
      </html>,
    ),
  )
})

export default app

 main.tsx

import { createRoot } from 'react-dom/client'
import App from './app'

const rootElement = document.getElementById('root')
const root = rootElement ? createRoot(rootElement) : console.error('Root element not found')

if (root) {
  root.render(<App />)
}

 デプロイ

pnpm run deploy

Demo

RPCの動作が確認できます。

const onSubmit = async () => {
    const res = await client.$get()
    const data = await res.json()
    setMessage(data.message)
  }

おわりに

 はじめて、Cloudflareを使用してみましたが、シンプルで扱いやすい印象でした。これから、Cloudflareを使用する方の参考になれば幸いです。

参考

Discussion