# VoidでWebアプリを作ろう!
PHPMatsuri Reboot in Hackone
Cloudflare / VoidZero - Developer Advocate / Yusuke Wada
# Yusuke Wada
- 和田裕介
- Cloudflare / VoidZero - Developer Advocate
- Honoの作者

# 今日やること
- Voidのオーバービュー
- プロジェクトを作って動かす
- コーディングエージェントとHono CLIを用意する
- お題のアプリを作る
- デプロイする
# Voidとは?
Void is a CLI, platform, and full-stack SDK that unifies building & deploying apps.
# 3つの顔がある
- フレームワーク - フルスタックのWebアプリを書くためのSDK
- プラットフォーム - チームのアプリをまとめてCloudflareにデプロイする基盤
- CLI - 作る・動かす・デプロイするを全部やる
voidコマンド
Build with Vite.
Deploy with Void.
# フレームワークとして
- Viteのプラグインとして動く
- UIは React / Vue / Svelte / Solid から選べる
- サーバー側のコードも同じプロジェクトに書く
- Cloudflare Workersで動く
# 最初から入っているもの
- ファイルベースのルーティング
- データベース(D1 + Drizzle ORM)
- 認証(Better Auth)
- KV / ストレージ / キュー / Cron
- AI / メール / WebSocket
txt
routes/ APIエンドポイント
pages/ ページ(UI)
db/ スキーマとマイグレーション
middleware/ ミドルウェア
crons/ 定期実行ジョブ
queues/ キューのコンシューマー
# PHPerにも馴染みがあるはず
- ページの仕組みは Inertia.js にインスパイアされている
void gen modelでテーブルとAPIの雛形ができる- マイグレーション、キュー、Cron、メールが最初からある
- 「全部入り」の感じはLaravelっぽい
# APIを書く
ファイルのパスがそのままURLになる
ts
// routes/api/hello.ts
import { defineHandler } from 'void'
export const GET = defineHandler(() => {
return { message: 'Hello from Void' }
})
/api/helloでJSONが返る- 中身はHonoで動いている
# ページを書く
サーバーの loader が返した値が、そのままコンポーネントのpropsになる
ts
// pages/index.server.ts
import { defineHandler, type InferProps } from 'void'
import { db, desc } from 'void/db'
import { messages } from '@schema'
export type Props = InferProps<typeof loader>
export const loader = defineHandler(async () => {
const rows = await db.select().from(messages).orderBy(desc(messages.id))
return { messages: rows }
})
# ページを書く
tsx
// pages/index.tsx
import type { Props } from './index.server'
export default function HomePage({ messages }: Props) {
return (
<ul>
{messages.map((message) => (
<li key={message.id}>{message.text}</li>
))}
</ul>
)
}
messagesの型はDBのスキーマから付いている
# ページの仕組み
同じURLが、2通りの返事をする
- 最初のアクセスには HTML
- ページを移動するときは JSON(コンポーネント名とprops)
APIを別に作らなくても、SPAのように動く
Inertia.js と同じ考え方
# 見てみる
sh
npx hono request / # HTML
npx hono request / -H 'x-voidpages: true' # JSON
json
{
"component": "index",
"props": { "messages": [{ "id": 1, "text": "hello", "createdAt": "2026-11-05 10:49:08" }] },
"params": {},
"shared": {},
"url": "/",
"errors": null
}
# データベース
テーブルはTypeScriptで定義する
ts
// db/schema.ts
import { sql } from 'void/db'
import { integer, sqliteTable, text } from 'void/schema-d1'
export const messages = sqliteTable('messages', {
id: integer('id').primaryKey({ autoIncrement: true }),
text: text('text').notNull(),
createdAt: text('created_at').notNull().default(sql`(datetime('now'))`),
})
# importしたら使える
ts
import { db } from 'void/db'
import { kv } from 'void/kv'
import { storage } from 'void/storage'
import { ai } from 'void/ai'
- 設定ファイルにリソースを書かなくていい
- ローカルでは勝手にローカルのDBが動く
- デプロイするときに本番のリソースが作られる
# 型がつながる
- DBのスキーマ →
loader→ コンポーネントのprops - APIのレスポンス → クライアントの
fetch
ts
import { fetch } from 'void/client'
const { message } = await fetch('/api/hello')
フィールドを変えると、直すべき場所をTypeScriptが教えてくれる
# CLIとして
sh
void init # プロジェクトを作る、既存のViteアプリに足す
void gen model # テーブルとAPIの雛形を作る
void db generate # マイグレーションを作る
void secret put # 本番のシークレットを設定する
void deploy # ビルドして公開する
void project logs # 本番のログを見る
voidパッケージに入っているので、CLIとアプリのバージョンが揃う- 困ったら
npx void --help
# プラットフォームとして
デプロイ先は2つ
- 自分のCloudflareアカウント(今日はこっち)
- チームで立てる「Voidプラットフォーム」
チームのほうは、認証情報を管理者だけが持つ
メンバーは void deploy するだけ
# 作ってみよう
# 必要なもの
- Node.js(24.22以上、または26.12以上)
- コーディングエージェント(Claude Code、Codexなど)
- Cloudflareのアカウント(無料プランでOK)
sh
node -v
# Cloudflareのアカウントを作る
- https://dash.cloudflare.com/sign-up を開く
- メールアドレスとパスワードを入れて「Create Account」
- 届いたメールのリンクを開いて、メールアドレスを認証する
- 無料で作れる
- 無料プランでも1日10万リクエストまで動かせる
# プロジェクトを作る
sh
npm create void@latest my-app
聞かれたら、こう選ぶ
txt
◇ Initialize a git repository? Yes
◇ Choose a Pages scaffold toolchain Vite+
◇ Choose a Pages framework React
◇ Choose a Pages starter D1 (SQLite)
◇ Where should this project deploy? Cloudflare
◇ Create .github/workflows/void-deploy.yml? No
# コーディングエージェントと一緒に
void initがAGENTS.mdを作ってくれている- Voidのドキュメントは
node_modules/void/docs/に入っている - エージェント向けのスキルもリンクされる
- つまり、エージェントはインストールされているバージョンのドキュメントを読んで書く
# Hono CLIも入れる
sh
npx @hono/cli@next agent-init
- Hono CLIは、コーディングエージェントのためのCLI
- VoidはHonoで動いているので、そのまま使える
@hono/cliが入り、AGENTS.mdに「変更はHono CLIで確認して」と書き込まれる
# エージェントはこう確認する
sh
npx hono routes # ルートの一覧
npx hono request /api/hello # リクエストを送る
npx hono batch - # POSTしてからGET、のような流れ
npx hono browse / # ページのJavaScriptまで動かす
npx hono diff # 直前のコミットから何が変わったか
- サーバーもブラウザもcurlもいらない
- 結果はJSONで返るので、エージェントが読みやすい
# こんな結果が返る
sh
npx hono request /api/hello
json
{
"ok": true,
"data": {
"status": 200,
"headers": { "content-type": "application/json" },
"body": { "message": "Hello from Void", "framework": "React", "database": "D1" },
"runtime": "vite"
}
}
# お題
あなたの考える最強の◯◯アプリ
# たとえば
- 最強のスライドアプリ
- 発表者がめくると、みんなの画面もめくれる
- 最強のTODOアプリ
- 雑なメモをAIがタスクに分けてくれる
- 最強のブックマークアプリ
- URLを入れるとAIが要約してタグを付ける
ちなみに、このスライドも Void と @void/md で動いています
# 最強にするキモ
AIと、Cloudflareのスタックがそのまま使える
- AI -
void/ai - データベース -
void/db - ファイル置き場 -
void/storage - リアルタイム -
void/live、WebSocket - 定期実行、キュー -
crons/、queues/
# エージェントへの頼み方
最終形態をしっかり想像する
想像できないものはつくれない
txt
ブックマークアプリを作りたい
トップにURLの入力欄がひとつ
保存すると一覧の先頭に出る
一覧にはタイトル、AIが書いた3行の要約、タグが並ぶ
タグを押すと、そのタグのブックマークだけになる
さあ始めよう!
# 参考
- Void - https://void.cloud
- Void GitHub - https://github.com/voidzero-dev/void
- Hono CLI - https://github.com/honojs/cli
- Discord - https://discord.gg/cC6TEVFKSx
- X - https://x.com/deploytovoid