From 7746938a421c2c6c90b810987e23a38f0ad74864 Mon Sep 17 00:00:00 2001 From: songtianlun Date: Sat, 2 Aug 2025 23:27:28 +0800 Subject: [PATCH] dep to cf --- README-WORKERS.md | 124 ++++++++++++++++++++++++ _worker.js | 17 ++++ next.config.ts | 19 ++++ package.json | 10 +- src/app/api/auth/callback/route.edge.ts | 55 +++++++++++ src/lib/prisma-edge.ts | 14 +++ wrangler.toml | 23 +++++ 7 files changed, 260 insertions(+), 2 deletions(-) create mode 100644 README-WORKERS.md create mode 100644 _worker.js create mode 100644 src/app/api/auth/callback/route.edge.ts create mode 100644 src/lib/prisma-edge.ts create mode 100644 wrangler.toml diff --git a/README-WORKERS.md b/README-WORKERS.md new file mode 100644 index 0000000..9138815 --- /dev/null +++ b/README-WORKERS.md @@ -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) \ No newline at end of file diff --git a/_worker.js b/_worker.js new file mode 100644 index 0000000..dd60020 --- /dev/null +++ b/_worker.js @@ -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, + }); + }, +}; \ No newline at end of file diff --git a/next.config.ts b/next.config.ts index d762d9f..7a7d589 100644 --- a/next.config.ts +++ b/next.config.ts @@ -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); diff --git a/package.json b/package.json index 23b3753..bfb423e 100644 --- a/package.json +++ b/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" } } \ No newline at end of file diff --git a/src/app/api/auth/callback/route.edge.ts b/src/app/api/auth/callback/route.edge.ts new file mode 100644 index 0000000..3a7177a --- /dev/null +++ b/src/app/api/auth/callback/route.edge.ts @@ -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`) +} \ No newline at end of file diff --git a/src/lib/prisma-edge.ts b/src/lib/prisma-edge.ts new file mode 100644 index 0000000..3511601 --- /dev/null +++ b/src/lib/prisma-edge.ts @@ -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 \ No newline at end of file diff --git a/wrangler.toml b/wrangler.toml new file mode 100644 index 0000000..28890b5 --- /dev/null +++ b/wrangler.toml @@ -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" \ No newline at end of file