前言
本文记录了使用 Cloudflare CLI(Wrangler)从零开始创建并部署一个 Nuxt 项目到 Cloudflare Pages 的全过程,同时配合 Hono + D1 搭建后端 API 服务。整个技术栈均为 Cloudflare 原生生态,无需传统服务器。
项目架构
我们最终要构建的架构如下:
- 前端:Nuxt 3 SSR 应用,部署在 Cloudflare Pages
- 后端 API:Hono 框架 + TypeScript,运行在 Cloudflare Workers
- 数据库:Cloudflare D1(基于 SQLite 的全球分布式数据库)
- 域名:cxyuan.asia → Pages,api.cxyuan.asia → Worker
这种方式的好处是:所有服务都在 Cloudflare 全球 330+ 个节点上运行,无需管理服务器,天然具备 CDN 加速和 DDoS 防护。
1. 安装 Wrangler CLI
Wrangler 是 Cloudflare 官方命令行工具,用于管理 Workers、Pages、D1 等资源。
# 全局安装
npm install -g wrangler
# 或项目内安装
npm install --save-dev wrangler
# 验证版本
wrangler --version
Wrangler v4 是目前的最新版本,支持完整的 Workers + Pages + D1 管理功能。
2. 登录 Cloudflare
安装完成后,需要登录你的 Cloudflare 账号:
wrangler login
执行后浏览器会自动打开 Cloudflare OAuth 授权页面,点击「Authorize」即可。登录后可以查看当前身份:
wrangler whoami
输出示例:
👋 You are logged in with an OAuth Token, associated with the email your@email.com.
┌───────────────────────────┬──────────────────────────────────┐
│ Account Name │ Account ID │
├───────────────────────────┼──────────────────────────────────┤
│ Your Account │ abc123... │
└───────────────────────────┴──────────────────────────────────┘
如果发现权限不足,可以重新登录:
wrangler login
登录时会自动请求全部最新的权限 scope。
3. 创建 Nuxt 项目
使用 npx 创建一个 Nuxt 3 项目:
npx nuxi init cxy-app
cd cxy-app
npm install
添加必要的依赖:
npm install axios nprogress
npm install --save-dev @types/nprogress less vite-plugin-prismjs
4. 配置 Cloudflare Pages 部署
在 nuxt.config.ts 中设置 Cloudflare Pages 预设:
export default defineNuxtConfig({
nitro: {
preset: "cloudflare_pages",
},
routeRules: {
"/**": { swr: 60 },
},
})
注意:routeRules 中的 swr 会让 Nitro 缓存 SSR 输出的 HTML。如果不需要,可以移除。
在 package.json 中添加构建脚本:
{
"scripts": {
"build": "nuxt build --dotenv .env.production"
}
}
创建 .env.production 文件:
NUXT_BASE_URL = https://api.cxyuan.asia/api/v1
5. 配置运行时 API 地址
在 nuxt.config.ts 中添加运行时配置:
runtimeConfig: {
public: {
apiBase: process.env.NUXT_BASE_URL,
},
}
创建 Axios 插件 plugins/axios.ts,在请求时自动注入 Authorization 令牌:
import axios from "axios";
import { getCookie } from "h3";
export default defineNuxtPlugin((nuxtApp) => {
const baseURL = nuxtApp.$config.public.apiBase;
const instance = axios.create({
baseURL,
timeout: 50000,
headers: { "Content-Type": "application/json" },
});
instance.interceptors.request.use((config) => {
let token = null;
if (import.meta.client) {
token = localStorage.getItem("token");
} else {
const event = nuxtApp.ssrContext?.event;
if (event) token = getCookie(event, "token") || null;
}
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
instance.interceptors.response.use(
(response) => response.data,
(error) => {
if (error.response && error.response.data) {
return Promise.resolve(error.response.data);
}
return Promise.reject(error);
},
);
nuxtApp.provide("axios", instance);
});
6. 创建 Hono Worker 后端
用 Wrangler 初始化 Worker 项目:
mkdir cxy-server-hono && cd cxy-server-hono
npm init -y
npm install hono
npm install --save-dev wrangler typescript @cloudflare/workers-types
npx wrangler init
wrangler.toml 配置:
name = "cxy-server"
main = "src/index.ts"
compatibility_date = "2025-03-01"
compatibility_flags = ["nodejs_compat"]
[[d1_databases]]
binding = "DB"
database_name = "cxy-db"
database_id = "your-database-id"
[vars]
UPLOAD_BUCKET = "static-cxyuan"
UPLOAD_CDN_DOMAIN = "https://static.cxyuan.asia/"
# 敏感信息使用 wrangler secret put
# wrangler secret put JWT_SECRET
# wrangler secret put UPLOAD_ACCESS_KEY
7. 创建 D1 数据库
# 创建数据库
npx wrangler d1 create cxy-db
# 执行建表 SQL
npx wrangler d1 execute cxy-db --file=./db/schema.sql --remote
建表 SQL 示例:
CREATE TABLE articles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
slug TEXT NOT NULL UNIQUE,
content TEXT DEFAULT NULL,
status TEXT DEFAULT 'draft',
visibility TEXT DEFAULT 'public',
published_at TEXT DEFAULT NULL,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
deleted_at TEXT DEFAULT NULL
);
8. 主入口文件:中间件与缓存
在 src/index.ts 中,我们配置了 CORS、日志、初始化以及基于 caches.default 的响应缓存:
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { initDB } from './config/database'
import { getCacheVersion } from './config/cache'
import routes from './routes'
const app = new Hono()
app.use('*', cors())
app.use('*', logger())
const cacheExclude = ['/captcha/', '/auth/']
app.use('/api/v1/*', async (c, next) => {
const url = new URL(c.req.url)
const shouldCache = c.req.method === 'GET'
&& !c.req.header('Authorization')
&& !cacheExclude.some(p => url.pathname.includes(p))
const cacheKeyUrl = new URL(url.href)
cacheKeyUrl.searchParams.set('_cv', getCacheVersion())
const cacheKey = new Request(cacheKeyUrl.href, { method: 'GET' })
if (shouldCache) {
const cached = await caches.default.match(cacheKey)
if (cached) return cached
}
await next()
if (shouldCache && c.res.status === 200) {
const clone = c.res.clone()
const bodyText = await clone.text()
try {
const json = JSON.parse(bodyText)
if (json.code === 200) {
const cacheRes = new Response(bodyText, {
status: clone.status,
headers: clone.headers,
})
cacheRes.headers.set('Cache-Control', 'public, s-maxage=300, stale-while-revalidate=60')
c.executionCtx.waitUntil(caches.default.put(cacheKey, cacheRes))
}
} catch {
// 非 JSON 响应也缓存
}
}
})
这个中间件实现了三级保护:
- 仅缓存 GET 请求,排除带 Authorization 头(管理员)的请求
- 排除验证码和登录接口
- 缓存键带版本号(
_cv),部署新版本时自动失效 - 仅缓存
code === 200的正常响应,错误/空响应不缓存
9. 时区问题与批量修复
Cloudflare Workers 默认使用 UTC 时间,而 D1 的 datetime('now') 也返回 UTC。对于中国用户,所有时间显示会晚 8 小时。
解决方案:创建东八区工具函数:
const SHANGHAI_OFFSET_MS = 8 * 60 * 60 * 1000
export function nowShanghai(): string {
const d = new Date(Date.now() + SHANGHAI_OFFSET_MS)
return d.toISOString().replace('T', ' ').split('.')[0]
}
export function todayShanghai(): string {
const d = new Date(Date.now() + SHANGHAI_OFFSET_MS)
return d.toISOString().split('T')[0]
}
然后批量替换所有 datetime('now') 为 nowShanghai(),并对已有数据执行 SQL 迁移:
UPDATE articles SET
published_at = datetime(published_at, '+8 hours'),
created_at = datetime(created_at, '+8 hours'),
updated_at = datetime(updated_at, '+8 hours');
10. 绑定自定义域名
在 Cloudflare Dashboard 中为 Worker 绑定自定义域名:
- 进入 Workers & Pages → 选择你的 Worker
- Triggers → Custom Domains → Add Custom Domain
- 输入
api.cxyuan.asia,Cloudflare 会自动添加 DNS 记录
11. 部署
Worker 部署:
npm run deploy
# 等同于 wrangler deploy
Nuxt 项目通过 Git 集成自动部署:
git add .
git commit -m "init: nuxt cloudflare pages"
git push origin main
在 Cloudflare Dashboard 的 Pages 中关联 Git 仓库,设置构建命令为 npm run build,输出目录为 .output/public。每次 push 到 main 分支就会自动构建部署。
12. 检查部署状态
查询 Worker 的所有部署版本:
npx wrangler deployments list
查询 Pages 部署:
npx wrangler pages deployment list --project-name cxy-app
13. 设置缓存规则(可选)
对于 Pages 站点(cxyuan.asia),可以在 Cloudflare Dashboard 中添加 Cache Rule:
- Rule name:首页缓存
- When:Hostname equals
cxyuan.asia - Then:Edge TTL = 5 minutes
对于 Workers 后端,由于自定义域名不走标准边缘缓存,需在代码内使用 caches.default 自行管理缓存。
14. Wrangler 常用命令速查
# 部署
wrangler deploy
# 查看部署历史
wrangler deployments list
# 回滚到指定版本
wrangler rollback <version-id>
# 管理密钥
echo "your-secret" | wrangler secret put MY_SECRET
# D1 数据库操作
wrangler d1 execute cxy-db --command "SELECT * FROM articles" --remote
# 本地开发
wrangler dev
nuxt dev --host
# Pages 项目列表
wrangler pages project list
# 查看当前身份与权限
wrangler whoami
结语
通过 Cloudflare 原生生态,我们仅用几行配置就完成了一个全栈应用的全球部署:Nuxt SSR 前端 + Hono API 后端 + D1 数据库,全部运行在 Cloudflare 的全球边缘网络上。无需管理服务器、无需配置负载均衡、无需操心 CDN。
这套架构特别适合:个人博客、小型商业网站、SaaS 应用 MVP 版本。当业务增长后,D1 可平滑迁移到 Hyperdrive + 传统数据库,Workers 可扩展到更多微服务。
