dep to cf
This commit is contained in:
parent
774f0ecb33
commit
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 withNextIntl = createNextIntlPlugin('./src/i18n/config.ts');
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
|
// Cloudflare Workers configuration
|
||||||
|
experimental: {
|
||||||
|
serverComponentsExternalPackages: ['@prisma/client'],
|
||||||
|
},
|
||||||
|
|
||||||
images: {
|
images: {
|
||||||
|
// Disable Image Optimization API for Workers compatibility
|
||||||
|
unoptimized: true,
|
||||||
remotePatterns: [
|
remotePatterns: [
|
||||||
{
|
{
|
||||||
protocol: 'https',
|
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);
|
export default withNextIntl(nextConfig);
|
||||||
|
10
package.json
10
package.json
@ -9,6 +9,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm run db:generate && next dev",
|
"dev": "npm run db:generate && next dev",
|
||||||
"build": "npm run db:generate && next build",
|
"build": "npm run db:generate && next build",
|
||||||
|
"build:workers": "npm run db:generate && next build && npm run workers:build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"db:generate": "prisma generate",
|
"db:generate": "prisma generate",
|
||||||
@ -17,10 +18,14 @@
|
|||||||
"db:studio": "prisma studio",
|
"db:studio": "prisma studio",
|
||||||
"db:reset": "prisma migrate reset",
|
"db:reset": "prisma migrate reset",
|
||||||
"db:seed": "prisma db seed",
|
"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": {
|
"dependencies": {
|
||||||
"@prisma/client": "^6.12.0",
|
"@prisma/client": "^6.12.0",
|
||||||
|
"@prisma/extension-accelerate": "^1.2.1",
|
||||||
"@supabase/auth-ui-react": "^0.4.7",
|
"@supabase/auth-ui-react": "^0.4.7",
|
||||||
"@supabase/auth-ui-shared": "^0.1.8",
|
"@supabase/auth-ui-shared": "^0.1.8",
|
||||||
"@supabase/ssr": "^0.6.1",
|
"@supabase/ssr": "^0.6.1",
|
||||||
@ -43,6 +48,7 @@
|
|||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.4.4",
|
"eslint-config-next": "15.4.4",
|
||||||
"tailwindcss": "^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