是的,你沒看錯——我在一天之內設計並實現了一個現代的、無伺服器的 JavaScript 全端框架。
這不是標題黨。我這麼做不是為了炫耀或給誰留下深刻印象——我只是需要它來完成我正在做的一個專案。
該專案是一個創新的、人工智慧優先的檢索資料庫,旨在無伺服器執行並利用Cloudflare 的邊緣網路。
因為它可以免費啟動,規模化營運更便宜,並能為您提供開箱即用的全球性能——非常適合現代邊緣原生應用程式。
該框架的強大功能如下:
後端: Hono——輕量級、快速且可擴展。
前端: React – 我快速建立 UI/UX 的首選。
建構系統: Vite-後端和前端都速度極快。
執行時期:本地 Node.js,生產環境中的 Cloudflare Workers。
套件生態系統: NPM——最大程度地相容於 JS 庫。
部署: Cloudflare Workers(後端)+ Cloudflare Pages(前端)。
該堆疊與Vercel 的 Next.js或OpenNext等流行的現代堆疊並駕齊驅,但限制更少,並且不受供應商鎖定。
您將獲得:
✅ 由 Cloudflare Workers 支援的邊緣功能
✅ 高效能、可擴充的程式碼庫
✅ 優秀的 DX(開發者體驗)
最重要的是 — —您可以自由建造和擴展,而不受限制。
我們需要八件事來實現這一點:
後端:Hono
前端: React
捆綁:白色
後端-前端連結: Vite
執行時:本地節點,生產環境中的 Workers
生態系: NPM
部署: Cloudflare Workers + Pages
開發人員:(那就是我-技術奇才艾哈邁德。😄)
我們將遵循monorepo 結構,包括:
packages/
→ 包含client/
和server/
types/
→ packages/
根設定檔( package.json
、 tsconfig.json
等)
這裡我們定義連結後端、前端和共享包的配置。
包.json
{
"name": "my-hono-react-app",
"private": true,
"type": "module",
"workspaces": [
"packages/*",
"types"
],
"scripts": {
"dev": "concurrently \"npm run dev:client\" \"npm run dev:server\"",
"dev:client": "npm run dev -w client",
"dev:server": "npm run dev -w server",
"watch": "concurrently \"npm run watch -w client\" \"npm run watch -w server\"",
"build": "npm run build -w client && npm run build -w server",
"deploy": "npm run deploy -w server"
},
"devDependencies": {
"@rollup/rollup-win32-x64-msvc": "^4.50.1",
"concurrently": "^8.2.2",
"wrangler": "^3.0.0"
},
"dependencies": {
"lightningcss": "^1.30.1"
}
}
💡 注意:如果您使用的是 Windows,則需要@rollup/rollup-win32-x64-msvc。
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"useDefineForClassFields": true,
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
},
"include": ["src/**/*", "*.ts"],
}
wrangler.toml
name = "hono-react-app"
main = "src/server/index.ts"
compatibility_date = "2024-01-01"
compatibility_flags = ["nodejs_compat"]
[vars]
NODE_ENV = "development"
[env.production]
[env.production.vars]
NODE_ENV = "production"
# For serving static assets in production
[assets]
directory = "./dist/client"
binding = "ASSETS"
[build]
command = "npm run build"
# For development
[dev]
port = 8787
(是的,你仍然需要你的.gitignore
和README.md
— jk,讓我們繼續吧😄)
types
資料夾儲存客戶端和伺服器共享的程式碼。您可以根據需要建立多個這樣的資料夾。
包.json
{
"name": "types",
"private": true,
"type": "module",
"types": "./index.d.ts",
"files": [
"**/*.d.ts",
"**/*.ts"
],
"scripts": {
"build": "tsc --project tsconfig.json"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"declaration": true,
"declarationMap": true,
"outDir": "../dist-types",
"rootDir": ".",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": [
"**/*.ts"
],
"exclude": [
"node_modules",
"dist-types"
]
}
包.json
{
"name": "server",
"type": "module",
"scripts": {
"dev": "wrangler dev",
"deploy": "wrangler deploy --minify",
"build": "npm run build -w client && tsc --noEmit",
"watch": "tsc --noEmit --watch --preserveWatchOutput",
"build:server": "tsc && cp -r ../dist/client ./dist/",
"cf-typegen": "wrangler types --env-interface CloudflareBindings"
},
"dependencies": {
"hono": "^4.9.6",
"types": "../types"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.0.0",
"wrangler": "^4.4.0"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2022"],
"types": ["@cloudflare/workers-types"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@/types": ["../../types/index"]
},
"noEmit": true,
},
"include": ["src/**/*"], // ONLY server files
"exclude": ["node_modules", "dist", "../client"] // Explicitly exclude client
}
wrangler.jsonc
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "server",
"main": "src/index.ts",
"compatibility_date": "2025-09-11",
"pages_build_output_dir": "./dist" // for cloudflare pages main for workers
// "compatibility_flags": [
// "nodejs_compat"
// ],
// "vars": {
// "MY_VAR": "my-variable"
// },
// "kv_namespaces": [
// {
// "binding": "MY_KV_NAMESPACE",
// "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
// }
// ],
// "r2_buckets": [
// {
// "binding": "MY_BUCKET",
// "bucket_name": "my-bucket"
// }
// ],
// "d1_databases": [
// {
// "binding": "MY_DB",
// "database_name": "my-database",
// "database_id": ""
// }
// ],
// "ai": {
// "binding": "AI"
// },
// "observability": {
// "enabled": true,
// "head_sampling_rate": 1
// }
}
src/index.ts
這裡我們需要兩樣東西,一種是使用 Hono 來定義後端 API,另一個設定是透過 React 和 Vite 啟用伺服器端渲染。
import { Hono } from "hono";
import { serveStatic } from "hono/cloudflare-workers";
import { cors } from "hono/cors";
const app = new Hono();
// Create a simple manifest object
const staticManifest = {};
// Cross origin
app.use(
"/api/*",
cors({
origin: ["http://localhost:3000", "http://localhost:5173"], // Allow both client ports
credentials: true,
})
);
// Serve static assets from the client build
app.use(
"/assets/*",
serveStatic({
root: "./dist/client",
manifest: staticManifest,
})
);
app.use(
"/favicon.ico",
serveStatic({
path: "./dist/client/favicon.ico",
manifest: staticManifest,
})
);
app.get("/api/hello", (c) => {
return c.json({
message: "Hello from Hono API!",
timestamp: new Date().toISOString(),
});
});
app.get("*", async (c) => {
try {
// Dynamically import the built server entry
// @ts-ignore - This file is generated during build
const { render } = await import("../dist/server/entry-server.js");
const url = new URL(c.req.url);
const { html, state } = await render(url.pathname, {});
const template = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Hono + React + Vite</title>
</head>
<body>
<div id="root">${html}</div>
<script>window.__INITIAL_STATE__ = ${state}</script>
<script type="module" src="/assets/client.js"></script>
</body>
</html>`;
return c.html(template);
} catch (e) {
console.error("SSR Error:", e);
return c.html("Server Error", 500);
}
});
export default app;
我們使用以下方法建立客戶端:
cd packages
npm create vite@latest
然後更新tsconfig.app.json
以新增類型路徑。
頁面/首頁.tsx
這不是 Next.js 頁面資料夾 - 只是組織程式碼的方式。
我們使用
react-router
進行路由以獲得具有 SSR 功能的類似 SPA 的感覺。
import { useQuery } from '@tanstack/react-query'
import { apiFetch } from '../api';
function HomePage() {
const { data: hello, isLoading } = useQuery({
queryKey: ['hello'],
queryFn: async () => {
const response = await apiFetch('/api/hello')
if (!response.ok) {
throw new Error('Network response was not ok')
}
return response.json()
}
});
return (
<div className="home-container">
<div className="text-center">
<h1 className="main-title">
Welcome to the Modern Stack
</h1>
<p className="main-description">
Experience NextJS-like DX with Hono, Cloudflare Workers, Vite, and React.
Built for speed, deployed to the edge.
</p>
</div>
<div className="features-grid">
<FeatureCard
title="⚡ Lightning Fast"
description="Powered by Cloudflare Workers with global edge deployment"
/>
<FeatureCard
title="🔥 Hot Reload"
description="Instant updates during development with Vite"
/>
<FeatureCard
title="🛡️ Type Safe"
description="Full TypeScript support across client and server"
/>
<FeatureCard
title="🌐 Edge Computing"
description="Run code closer to your users for minimal latency"
/>
<FeatureCard
title="📦 Zero Config"
description="Sensible defaults with easy customization"
/>
<FeatureCard
title="🚀 Serverless"
description="No servers to manage, scales automatically"
/>
</div>
{/* API Status */}
<div className="api-status-card">
<h3 className="api-status-title">
API Status
</h3>
{isLoading ? (
<div className="loading-pulse">
<div className="pulse-line pulse-line-1"></div>
<div className="pulse-line pulse-line-2"></div>
</div>
) : hello ? (
<div className="api-status-content">
<div className="status-indicator">
<div className="status-dot status-connected"></div>
<span className="status-text">Connected to Hono API</span>
</div>
<p className="api-response">
Response: {hello.message}
</p>
<p className="api-timestamp">
Timestamp: {hello.timestamp}
</p>
</div>
) : (
<div className="status-indicator">
<div className="status-dot status-error"></div>
<span className="status-text">API connection failed</span>
</div>
)}
</div>
</div>
)
}
function FeatureCard({ title, description }: { title: string; description: string }) {
return (
<div className="feature-card">
<h3 className="feature-title">{title}</h3>
<p className="feature-description">{description}</p>
</div>
)
}
export default HomePage
客戶端/src/entry-server.tsx
處理客戶端路由的伺服器端渲染。
import React from 'react'
import { renderToString } from 'react-dom/server'
import { StaticRouter } from 'react-router-dom/server'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import App from './App'
export function render(url: string, initialState : any) {
// Create a new QueryClient for each request
const queryClient = new QueryClient()
const html = renderToString(
<React.StrictMode>
<StaticRouter location={url}>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</StaticRouter>
</React.StrictMode>
)
// Serialize the state for hydration
const state = JSON.stringify(initialState).replace(/</g, '\\\\u003c')
return { html, state }
}
我們設定React Query進行狀態管理,以及React Router用於客戶端路由client/src/index.tsx :
import React from 'react'
import { hydrateRoot } from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import App from './App'
// Create a client for hydration
const queryClient = new QueryClient()
hydrateRoot(
document.getElementById('root') as HTMLElement,
<React.StrictMode>
<BrowserRouter>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</BrowserRouter>
</React.StrictMode>
)
在下面的 repo 連結中找到了 CSS...
就是這樣——我們剛剛建立了一個全端、無伺服器的 JavaScript 框架,可以為生產應用程式提供支援。
npm i
npm run build
npm run dev
節點版本:v20.17.0
完整原始碼連結:https://github.com/ARAldhafeeri/hono-react-vite-cloudflare
現在,您擁有一個現代的、無伺服器的堆棧,它快速、可擴展且對開發人員友好- 而不受商業平台的束縛。
原文出處:https://dev.to/araldhafeeri/i-built-a-modern-serverless-js-full-stack-framework-in-one-day-312k