dep to cf

This commit is contained in:
songtianlun 2025-08-02 23:27:28 +08:00
parent 774f0ecb33
commit 7746938a42
7 changed files with 260 additions and 2 deletions

124
README-WORKERS.md Normal file
View File

@ -0,0 +1,124 @@
# Cloudflare Workers Deployment Guide
本文档介绍如何将 PRMBR 项目部署到 Cloudflare Workers。
## 前置要求
1. Cloudflare 账户
2. Wrangler CLI 工具
3. Prisma Accelerate (用于数据库连接)
## 配置步骤
### 1. 安装依赖
```bash
npm install
```
### 2. 设置 Prisma Accelerate
由于 Cloudflare Workers 不支持直接的 PostgreSQL 连接,需要使用 Prisma Accelerate
1. 访问 [Prisma Console](https://console.prisma.io/)
2. 创建新项目或选择现有项目
3. 启用 Accelerate 功能
4. 获取 Accelerate 连接字符串:`prisma://accelerate.prisma-data.net/?api_key=your_api_key`
### 3. 设置环境变量
在 Cloudflare Dashboard 中设置以下环境变量:
```bash
# 使用 wrangler 命令设置
wrangler secret put NEXT_PUBLIC_SUPABASE_URL
wrangler secret put NEXT_PUBLIC_SUPABASE_ANON_KEY
wrangler secret put DATABASE_URL
```
或在 Cloudflare Dashboard 中:
Workers & Pages > Your Worker > Settings > Environment Variables
### 4. 构建和部署
```bash
# 构建用于 Workers 的版本
npm run build:workers
# 部署到 Cloudflare Workers
npm run workers:deploy
```
### 5. 本地开发 (Workers)
```bash
# 启动本地 Workers 开发服务器
npm run workers:dev
```
## 主要更改
### Next.js 配置 (next.config.ts)
- 启用 `output: 'export'` 用于静态导出
- 禁用图像优化 (`unoptimized: true`)
- 添加 webpack 配置以排除 Node.js 特定模块
### 数据库适配
- 创建 `src/lib/prisma-edge.ts` 使用 Prisma Accelerate
- 在需要数据库连接的 API 路由中使用 Edge Runtime
### 文件结构
```
├── _worker.js # Cloudflare Workers 入口点
├── wrangler.toml # Wrangler 配置
├── src/lib/prisma-edge.ts # Edge 运行时的 Prisma 客户端
└── out/ # 构建输出目录
```
## 限制和注意事项
1. **Runtime 限制**: Cloudflare Workers 使用 V8 JavaScript 引擎,不支持完整的 Node.js API
2. **冷启动**: 第一次请求可能较慢
3. **内存限制**: Workers 有内存和 CPU 时间限制
4. **数据库连接**: 必须使用 Prisma Accelerate 或其他兼容的数据库代理
5. **文件系统**: 无法访问文件系统,所有资源必须内联或通过网络获取
## 故障排除
### 构建错误
如果遇到构建错误,检查:
1. 所有依赖是否兼容 Edge Runtime
2. 是否使用了 Node.js 特定 API
3. 环境变量是否正确设置
### 运行时错误
1. 检查 Cloudflare Workers 日志
2. 确认数据库连接字符串正确
3. 验证所有环境变量都已设置
### 性能优化
1. 使用 Prisma Accelerate 的缓存功能
2. 优化数据库查询
3. 减少冷启动时间通过预热请求
## 部署检查清单
- [ ] Prisma Accelerate 已配置
- [ ] 环境变量已设置
- [ ] 构建成功完成
- [ ] 本地 Workers 开发测试通过
- [ ] 生产环境部署测试
- [ ] 数据库连接正常
- [ ] 认证流程工作正常
## 更多资源
- [Cloudflare Workers 文档](https://developers.cloudflare.com/workers/)
- [Prisma Accelerate 文档](https://www.prisma.io/data-platform/accelerate)
- [Next.js Edge Runtime 文档](https://nextjs.org/docs/app/api-reference/edge)

17
_worker.js Normal file
View File

@ -0,0 +1,17 @@
// Cloudflare Workers entry point for Next.js app
export default {
async fetch(request, env, ctx) {
// Import the Next.js handler
const { default: handler } = await import('./server.js');
// Handle the request with Next.js
return handler(request, {
env,
ctx,
// Pass environment variables
NEXT_PUBLIC_SUPABASE_URL: env.NEXT_PUBLIC_SUPABASE_URL,
NEXT_PUBLIC_SUPABASE_ANON_KEY: env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
DATABASE_URL: env.DATABASE_URL,
});
},
};

View File

@ -4,7 +4,14 @@ import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin('./src/i18n/config.ts');
const nextConfig: NextConfig = {
// Cloudflare Workers configuration
experimental: {
serverComponentsExternalPackages: ['@prisma/client'],
},
images: {
// Disable Image Optimization API for Workers compatibility
unoptimized: true,
remotePatterns: [
{
protocol: 'https',
@ -32,6 +39,18 @@ const nextConfig: NextConfig = {
}
],
},
// Webpack configuration for Workers compatibility
webpack: (config, { isServer }) => {
if (isServer) {
// Exclude Node.js specific modules
config.externals.push({
'utf-8-validate': 'commonjs utf-8-validate',
'bufferutil': 'commonjs bufferutil',
});
}
return config;
},
};
export default withNextIntl(nextConfig);

View File

@ -9,6 +9,7 @@
"scripts": {
"dev": "npm run db:generate && next dev",
"build": "npm run db:generate && next build",
"build:workers": "npm run db:generate && next build && npm run workers:build",
"start": "next start",
"lint": "next lint",
"db:generate": "prisma generate",
@ -17,10 +18,14 @@
"db:studio": "prisma studio",
"db:reset": "prisma migrate reset",
"db:seed": "prisma db seed",
"postinstall": "prisma generate"
"postinstall": "prisma generate",
"workers:dev": "wrangler dev",
"workers:deploy": "wrangler deploy",
"workers:build": "cp _worker.js out/_worker.js"
},
"dependencies": {
"@prisma/client": "^6.12.0",
"@prisma/extension-accelerate": "^1.2.1",
"@supabase/auth-ui-react": "^0.4.7",
"@supabase/auth-ui-shared": "^0.1.8",
"@supabase/ssr": "^0.6.1",
@ -43,6 +48,7 @@
"eslint": "^9",
"eslint-config-next": "15.4.4",
"tailwindcss": "^4",
"typescript": "^5"
"typescript": "^5",
"wrangler": "^3.80.0"
}
}

View File

@ -0,0 +1,55 @@
import { createServerClient } from '@supabase/ssr'
import { NextRequest, NextResponse } from 'next/server'
export const runtime = 'edge'
interface CookieOptions {
maxAge?: number;
httpOnly?: boolean;
secure?: boolean;
sameSite?: 'strict' | 'lax' | 'none';
path?: string;
}
export async function GET(request: NextRequest) {
const { searchParams, origin } = new URL(request.url)
const code = searchParams.get('code')
const next = searchParams.get('next') ?? '/'
if (code) {
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
get(name: string) {
return request.cookies.get(name)?.value
},
set(_name: string, _value: string, _options: CookieOptions) {
// Cookie setting handled by response headers in edge runtime
},
remove(_name: string, _options: CookieOptions) {
// Cookie removal handled by response headers in edge runtime
},
},
}
)
const { error } = await supabase.auth.exchangeCodeForSession(code)
if (!error) {
const forwardedHost = request.headers.get('x-forwarded-host')
const isLocalEnv = process.env.NODE_ENV === 'development'
if (isLocalEnv) {
return NextResponse.redirect(`${origin}${next}`)
} else if (forwardedHost) {
return NextResponse.redirect(`https://${forwardedHost}${next}`)
} else {
return NextResponse.redirect(`${origin}${next}`)
}
}
}
return NextResponse.redirect(`${origin}/auth/auth-code-error`)
}

14
src/lib/prisma-edge.ts Normal file
View File

@ -0,0 +1,14 @@
import { PrismaClient } from '@prisma/client/edge'
import { withAccelerate } from '@prisma/extension-accelerate'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prismaEdge =
globalForPrisma.prisma ??
new PrismaClient({
datasourceUrl: process.env.DATABASE_URL,
}).$extends(withAccelerate())
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prismaEdge

23
wrangler.toml Normal file
View File

@ -0,0 +1,23 @@
name = "prmbr"
main = "_worker.js"
compatibility_date = "2024-12-01"
compatibility_flags = ["nodejs_compat"]
[build]
command = "npm run build"
[site]
bucket = "./out"
[vars]
NODE_ENV = "production"
# Environment variables (set these in Cloudflare Dashboard)
# NEXT_PUBLIC_SUPABASE_URL = ""
# NEXT_PUBLIC_SUPABASE_ANON_KEY = ""
# DATABASE_URL = ""
[[r2_buckets]]
binding = "BUCKET"
bucket_name = "prmbr-storage"
preview_bucket_name = "prmbr-storage-preview"