Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
7746938a42 |
124
README-WORKERS.md
Normal file
124
README-WORKERS.md
Normal 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
17
_worker.js
Normal 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,
|
||||
});
|
||||
},
|
||||
};
|
@ -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);
|
||||
|
10
package.json
10
package.json
@ -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"
|
||||
}
|
||||
}
|
55
src/app/api/auth/callback/route.edge.ts
Normal file
55
src/app/api/auth/callback/route.edge.ts
Normal 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
14
src/lib/prisma-edge.ts
Normal 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
23
wrangler.toml
Normal 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"
|
Loading…
Reference in New Issue
Block a user