team-saas
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTeam SaaS Architecture Skill
团队SaaS架构Skill
Build production-grade multi-tenant SaaS applications with team workspaces, member invitation, authentication, and modern UI. Based on a proven architecture powering real production applications.
构建具备团队工作区、成员邀请、身份验证和现代UI的生产级多租户SaaS应用。基于已验证的架构,可为实际生产应用提供支撑。
When to Use This Skill
何时使用此Skill
Use when:
- Building a new SaaS product with team/workspace functionality
- Setting up multi-tenant authentication and authorization
- Implementing team member invitation flows
- Creating Linear/Vercel-style modern UI with light/dark mode
- Setting up background job processing for SaaS
- Deploying full-stack Next.js apps to Railway
适用于以下场景:
- 构建带有团队/工作区功能的新SaaS产品
- 搭建多租户身份验证与授权体系
- 实现团队成员邀请流程
- 创建Linear/Vercel风格的现代UI,支持明暗模式
- 为SaaS应用设置后台任务处理
- 将全栈Next.js应用部署至Railway
Tech Stack Overview
技术栈概览
| Layer | Technology | Version |
|---|---|---|
| Framework | Next.js (App Router) | 16.x |
| React | React | 19.x |
| Runtime | Bun | Latest |
| Database | PostgreSQL (Railway) | - |
| ORM | Prisma | 7.x |
| Auth | NextAuth v5 (Auth.js) | 5.0.0-beta |
| UI | shadcn/ui (new-york) | Latest |
| Styling | Tailwind CSS | v4 |
| Theming | next-themes | Latest |
| Icons | Lucide React | Latest |
| State | TanStack React Query | 5.x |
| Validation | Zod | 4.x |
| Resend | 6.x | |
| Storage | Railway S3-compatible | - |
| Jobs | pg-boss | 12.x |
| Hosting | Railway | - |
| 层级 | 技术栈 | 版本 |
|---|---|---|
| 框架 | Next.js (App Router) | 16.x |
| React | React | 19.x |
| 运行时 | Bun | Latest |
| 数据库 | PostgreSQL (Railway) | - |
| ORM | Prisma | 7.x |
| 身份验证 | NextAuth v5 (Auth.js) | 5.0.0-beta |
| UI组件库 | shadcn/ui (new-york) | Latest |
| 样式 | Tailwind CSS | v4 |
| 主题 | next-themes | Latest |
| 图标 | Lucide React | Latest |
| 状态管理 | TanStack React Query | 5.x |
| 校验 | Zod | 4.x |
| 邮件服务 | Resend | 6.x |
| 存储 | Railway S3-compatible | - |
| 任务队列 | pg-boss | 12.x |
| 托管平台 | Railway | - |
Architecture Diagram
架构图
┌─────────────────────────────────────────────────────────────────────┐
│ Next.js Application │
├─────────────────────────────────────────────────────────────────────┤
│ Landing Page │ Auth Pages │ Dashboard (Protected) │
│ / │ /login │ /teams/[teamId]/* │
│ /pricing │ /register │ /dashboard │
│ │ /invite/[t] │ /settings │
├─────────────────────────────────────────────────────────────────────┤
│ API Routes │
│ /api/auth/* │ /api/teams/* │ /api/uploads/* │
│ /api/cron/* │ /api/webhooks/* │ /api/invitations/* │
├─────────────────────────────────────────────────────────────────────┤
│ proxy.ts (Security Headers) │ layout.tsx (Auth Protection) │
├─────────────────────────────────────────────────────────────────────┤
│ Services Layer │
│ Prisma (DB) │ Resend (Email) │ S3 (Storage) │ pg-boss (Jobs) │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Railway Infrastructure │
│ PostgreSQL │ S3 Bucket │ Redis (optional) │
└─────────────────────────────────────────────────────────────────────┘┌─────────────────────────────────────────────────────────────────────┐
│ Next.js Application │
├─────────────────────────────────────────────────────────────────────┤
│ Landing Page │ Auth Pages │ Dashboard (Protected) │
│ / │ /login │ /teams/[teamId]/* │
│ /pricing │ /register │ /dashboard │
│ │ /invite/[t] │ /settings │
├─────────────────────────────────────────────────────────────────────┤
│ API Routes │
│ /api/auth/* │ /api/teams/* │ /api/uploads/* │
│ /api/cron/* │ /api/webhooks/* │ /api/invitations/* │
├─────────────────────────────────────────────────────────────────────┤
│ proxy.ts (Security Headers) │ layout.tsx (Auth Protection) │
├─────────────────────────────────────────────────────────────────────┤
│ Services Layer │
│ Prisma (DB) │ Resend (Email) │ S3 (Storage) │ pg-boss (Jobs) │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Railway Infrastructure │
│ PostgreSQL │ S3 Bucket │ Redis (optional) │
└─────────────────────────────────────────────────────────────────────┘Core Patterns
核心模式
1. Multi-Tenant Team Structure
1. 多租户团队结构
Every data entity belongs to a team. Users can be members of multiple teams with roles.
User ─┬─> TeamMember ─> Team ─┬─> Creator
│ ├─> Campaign
└─> TeamMember ─> Team ├─> Content
└─> ... (all team resources)每个数据实体都隶属于一个团队。用户可以以不同角色成为多个团队的成员。
User ─┬─> TeamMember ─> Team ─┬─> Creator
│ ├─> Campaign
└─> TeamMember ─> Team ├─> Content
└─> ... (all team resources)2. Route Groups
2. 路由分组
src/app/
├── (auth)/ # Public auth pages (login, register)
│ └── layout.tsx # Client component, styling only
├── (dashboard)/ # Protected authenticated routes
│ ├── layout.tsx # Server component with auth() check + redirect
│ └── teams/[teamId]/ # Team-scoped pages
├── (marketing)/ # Public marketing pages
├── invite/[token]/ # Team invitation acceptance
└── api/ # API routessrc/app/
├── (auth)/ # 公开的身份验证页面(登录、注册)
│ └── layout.tsx # 客户端组件,仅负责样式
├── (dashboard)/ # 受保护的已验证路由
│ ├── layout.tsx # 服务端组件,包含auth()检查与重定向
│ └── teams/[teamId]/ # 团队范围的页面
├── (marketing)/ # 公开的营销页面
├── invite/[token]/ # 团队邀请接受页面
└── api/ # API路由3. Route Protection Pattern (Layout-based, NOT middleware)
3. 路由保护模式(基于布局,而非中间件)
IMPORTANT: Auth protection is done via Server Component layout checks, NOT Edge middleware.
typescript
// src/app/(dashboard)/layout.tsx
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
export default async function DashboardLayout({ children }) {
const session = await auth();
if (!session?.user) {
redirect("/login");
}
return <DashboardShell session={session}>{children}</DashboardShell>;
}重要提示: 身份验证保护通过服务端组件布局检查实现,而非Edge中间件。
typescript
// src/app/(dashboard)/layout.tsx
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
export default async function DashboardLayout({ children }) {
const session = await auth();
if (!session?.user) {
redirect("/login");
}
return <DashboardShell session={session}>{children}</DashboardShell>;
}4. API Route Permission Pattern
4. API路由权限模式
typescript
// Use api-helpers for clean, consistent patterns
import { requireTeamMember, parseJsonBody, withErrorHandler, ApiError } from "@/lib/api-helpers";
type RouteParams = {
params: Promise<{ teamId: string }>; // Next.js 15+ async params
};
export const POST = withErrorHandler(async (req: NextRequest, { params }: RouteParams) => {
const { teamId } = await params; // MUST await params
const { userId, role } = await requireTeamMember(teamId); // Throws 401/403
if (role !== "ADMIN") {
throw new ApiError("Admin access required", 403);
}
const data = await parseJsonBody(req, mySchema); // Validates with Zod
// ... business logic
return NextResponse.json(result);
});typescript
// 使用api-helpers实现清晰、一致的模式
import { requireTeamMember, parseJsonBody, withErrorHandler, ApiError } from "@/lib/api-helpers";
type RouteParams = {
params: Promise<{ teamId: string }>; // Next.js 15+ 异步参数
};
export const POST = withErrorHandler(async (req: NextRequest, { params }: RouteParams) => {
const { teamId } = await params; // 必须await参数
const { userId, role } = await requireTeamMember(teamId); // 抛出401/403错误
if (role !== "ADMIN") {
throw new ApiError("Admin access required", 403);
}
const data = await parseJsonBody(req, mySchema); // 使用Zod校验
// ... 业务逻辑 ...
return NextResponse.json(result);
});File Structure
文件结构
src/
├── app/
│ ├── (auth)/ # Auth pages (public)
│ │ ├── login/page.tsx
│ │ ├── register/page.tsx
│ │ └── layout.tsx # Client component, no auth
│ ├── (dashboard)/ # Protected pages
│ │ ├── layout.tsx # Server component, auth check
│ │ ├── dashboard-shell.tsx # Sidebar + nav
│ │ └── teams/[teamId]/
│ ├── invite/[token]/page.tsx # Invitation acceptance
│ ├── api/
│ │ ├── auth/[...nextauth]/route.ts
│ │ ├── teams/
│ │ │ ├── route.ts # List/create teams
│ │ │ └── [teamId]/
│ │ │ ├── route.ts # Team CRUD
│ │ │ ├── members/
│ │ │ └── invitations/
│ │ ├── invitations/
│ │ │ └── [token]/accept/route.ts
│ │ └── uploads/[...path]/route.ts # S3 proxy
│ ├── globals.css # Design system
│ ├── layout.tsx # Root layout with Providers
│ └── page.tsx # Landing page
├── components/
│ ├── ui/ # shadcn components
│ ├── teams/ # Team management
│ ├── invitations/ # Invitation UI
│ ├── shared/ # Reusable patterns
│ ├── providers.tsx # App providers
│ └── theme-toggle.tsx # Theme switcher
├── hooks/
│ ├── use-teams.ts # Team hooks
│ └── ...
├── lib/
│ ├── auth.ts # NextAuth config (Node.js runtime)
│ ├── auth.config.ts # Auth config (callbacks, pages)
│ ├── api-helpers.ts # withErrorHandler, requireTeamMember, etc.
│ ├── prisma.ts # Prisma client (lazy proxy)
│ ├── s3.ts # S3 utilities
│ ├── resend.ts # Email client
│ ├── query-client.ts # React Query
│ ├── utils.ts # cn(), getInitials(), etc.
│ └── jobs/ # pg-boss setup
├── proxy.ts # Security headers (NOT auth)
├── types/
│ └── next-auth.d.ts # Auth type extensions
└── generated/
└── prisma/ # Prisma client outputsrc/
├── app/
│ ├── (auth)/ # 公开的身份验证页面(登录、注册)
│ │ ├── login/page.tsx
│ │ ├── register/page.tsx
│ │ └── layout.tsx # 客户端组件,无身份验证
│ ├── (dashboard)/ # 受保护的页面
│ │ ├── layout.tsx # 服务端组件,身份验证检查
│ │ ├── dashboard-shell.tsx # 侧边栏 + 导航
│ │ └── teams/[teamId]/
│ ├── invite/[token]/page.tsx # 邀请接受页面
│ ├── api/
│ │ ├── auth/[...nextauth]/route.ts
│ │ ├── teams/
│ │ │ ├── route.ts # 团队列表/创建
│ │ │ └── [teamId]/
│ │ │ ├── route.ts # 团队CRUD
│ │ │ ├── members/
│ │ │ └── invitations/
│ │ ├── invitations/
│ │ │ └── [token]/accept/route.ts
│ │ └── uploads/[...path]/route.ts # S3代理
│ ├── globals.css # 设计系统
│ ├── layout.tsx # 根布局,包含Providers
│ └── page.tsx # 着陆页
├── components/
│ ├── ui/ # shadcn组件
│ ├── teams/ # 团队管理组件
│ ├── invitations/ # 邀请相关UI
│ ├── shared/ # 可复用组件
│ ├── providers.tsx # 应用提供者
│ └── theme-toggle.tsx # 主题切换器
├── hooks/
│ ├── use-teams.ts # 团队相关hooks
│ └── ...
├── lib/
│ ├── auth.ts # NextAuth配置(Node.js运行时)
│ ├── auth.config.ts # 身份验证配置(回调、页面)
│ ├── api-helpers.ts # withErrorHandler、requireTeamMember等
│ ├── prisma.ts # Prisma客户端(懒加载代理)
│ ├── s3.ts # S3工具类
│ ├── resend.ts # 邮件客户端
│ ├── query-client.ts # React Query配置
│ ├── utils.ts # cn()、getInitials()等工具函数
│ └── jobs/ # pg-boss配置
├── proxy.ts # 安全头配置(非身份验证)
├── types/
│ └── next-auth.d.ts # 身份验证类型扩展
└── generated/
└── prisma/ # Prisma客户端输出Related Asset Files
相关资源文件
This skill includes ready-to-use templates:
| Asset | Description |
|---|---|
| NextAuth v5 with Credentials provider |
| Auth config (callbacks, custom pages) |
| withErrorHandler, requireTeamMember, ApiError |
| Lazy-loaded Prisma client with pg adapter |
| S3 utilities with presigned URLs |
| Resend email client + templates |
| React Query setup |
| pg-boss job queue |
| Utility functions |
| App providers |
| Light/dark/system toggle |
| Team dropdown |
| Linear-style design system |
| Production Docker build |
| Railway deployment |
| Next.js configuration |
| Security headers |
| Environment variables |
| shadcn configuration |
| Base team schema |
| Team API template |
| Invitations API template |
| React Query hooks |
| Type extensions |
此Skill包含可直接使用的模板:
| 资源文件 | 描述 |
|---|---|
| 带有Credentials提供者的NextAuth v5配置 |
| 身份验证配置(回调、自定义页面) |
| withErrorHandler、requireTeamMember、ApiError |
| 带有pg适配器的懒加载Prisma客户端 |
| 带有预签名URL的S3工具类 |
| Resend邮件客户端 + 模板 |
| React Query配置 |
| pg-boss任务队列 |
| 工具函数 |
| 应用提供者 |
| 明暗/系统主题切换器 |
| 团队下拉选择器 |
| Linear风格设计系统 |
| 生产环境Docker构建文件 |
| Railway部署配置 |
| Next.js配置 |
| 安全头配置 |
| 环境变量示例 |
| shadcn配置 |
| 基础团队Schema |
| 团队API模板 |
| 邀请API模板 |
| React Query hooks |
| 类型扩展 |
Setup Instructions
安装说明
1. Initialize Project
1. 初始化项目
bash
bunx create-next-app@latest my-saas --typescript --tailwind --eslint --app --src-dir
cd my-saasbash
bunx create-next-app@latest my-saas --typescript --tailwind --eslint --app --src-dir
cd my-saas2. Install Dependencies
2. 安装依赖
bash
undefinedbash
undefinedCore
核心依赖
bun add next-auth@beta @auth/prisma-adapter bcryptjs
bun add @prisma/client @prisma/adapter-pg pg
bun add -D prisma @types/bcryptjs
bun add next-auth@beta @auth/prisma-adapter bcryptjs
bun add @prisma/client @prisma/adapter-pg pg
bun add -D prisma @types/bcryptjs
UI
UI相关
bun add lucide-react
bun add next-themes class-variance-authority clsx tailwind-merge
bun add @tanstack/react-query
bun add lucide-react
bun add next-themes class-variance-authority clsx tailwind-merge
bun add @tanstack/react-query
Validation
校验工具
bun add zod
bun add zod
邮件服务
bun add resend
bun add resend
Storage
存储服务
bun add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
bun add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner
Jobs
任务队列
bun add pg-boss
bun add pg-boss
shadcn
shadcn组件
bunx shadcn@latest init
bunx shadcn@latest add button card dialog input label sonner tooltip dropdown-menu avatar badge form command popover
undefinedbunx shadcn@latest init
bunx shadcn@latest add button card dialog input label sonner tooltip dropdown-menu avatar badge form command popover
undefined3. Configure Environment
3. 配置环境变量
Copy and fill in values:
.env.examplebash
undefined复制并填写对应值:
.env.examplebash
undefinedRequired
必填项
DATABASE_URL="postgresql://..."
AUTH_SECRET="openssl rand -base64 32"
NEXTAUTH_URL="http://localhost:3000"
RESEND_API_KEY="re_..."
EMAIL_FROM="onboarding@resend.dev"
DATABASE_URL="postgresql://..."
AUTH_SECRET="openssl rand -base64 32"
NEXTAUTH_URL="http://localhost:3000"
RESEND_API_KEY="re_..."
EMAIL_FROM="onboarding@resend.dev"
S3 Storage (Railway)
S3存储(Railway)
AWS_ENDPOINT_URL="https://..."
AWS_ACCESS_KEY_ID="..."
AWS_SECRET_ACCESS_KEY="..."
AWS_S3_BUCKET_NAME="..."
AWS_DEFAULT_REGION="auto"
undefinedAWS_ENDPOINT_URL="https://..."
AWS_ACCESS_KEY_ID="..."
AWS_SECRET_ACCESS_KEY="..."
AWS_S3_BUCKET_NAME="..."
AWS_DEFAULT_REGION="auto"
undefined4. Setup Prisma
4. 配置Prisma
bash
undefinedbash
undefinedInitialize
初始化
bunx prisma init
bunx prisma init
Copy the schema from assets/prisma/schema.prisma
从assets/prisma/schema.prisma复制Schema
Ensure generator uses "prisma-client" (not "prisma-client-js")
确保generator使用"prisma-client"(而非"prisma-client-js")
Ensure output is "../src/generated/prisma"
确保输出路径为"../src/generated/prisma"
Push to database
推送至数据库
bunx prisma db push
bunx prisma generate
undefinedbunx prisma db push
bunx prisma generate
undefined5. Copy Asset Files
5. 复制资源文件
Copy the template files from to your project, adjusting imports as needed.
assets/将下的模板文件复制到你的项目中,按需调整导入路径。
assets/Design System
设计系统
Brand Colors
品牌颜色
Primary purple: (HSL: 262 83% 58%)
#8b5cf6主紫色:(HSL: 262 83% 58%)
#8b5cf6Light Mode
亮色模式
- Background:
#ffffff - Foreground:
hsl(240 10% 10%) - Border:
hsl(240 6% 90%)
- 背景色:
#ffffff - 前景色:
hsl(240 10% 10%) - 边框色:
hsl(240 6% 90%)
Dark Mode (Linear-style)
暗色模式(Linear风格)
- Background: (hsl 240 5% 4%)
#0A0A0B - Foreground:
hsl(0 0% 95%) - Border:
rgba(255,255,255,0.06)
- 背景色:(hsl 240 5% 4%)
#0A0A0B - 前景色:
hsl(0 0% 95%) - 边框色:
rgba(255,255,255,0.06)
Theme Toggle
主题切换器
Three-option segmented control with icons only:
- Sun icon → Light
- Moon icon → Dark
- Monitor icon → System
Located in sidebar footer or header. Uses with .
next-themesattribute="class"仅含图标的三选项分段控件:
- 太阳图标 → 亮色模式
- 月亮图标 → 暗色模式
- 显示器图标 → 系统模式
位于侧边栏底部或顶部。使用,配置。
next-themesattribute="class"Typography
排版
- Font: Geist Sans / Geist Mono
- Tight letter-spacing: -0.011em body, -0.02em headings
- Font smoothing: antialiased
- 字体:Geist Sans / Geist Mono
- 紧凑字间距:正文-0.011em,标题-0.02em
- 字体平滑:antialiased
API Route Patterns
API路由模式
Route Handler Signature (Next.js 15+)
路由处理器签名(Next.js 15+)
CRITICAL: In Next.js 15+, is a and MUST be awaited:
paramsPromisetypescript
type RouteParams = {
params: Promise<{ teamId: string }>;
};
export async function GET(req: NextRequest, { params }: RouteParams) {
const { teamId } = await params; // MUST await!
// ...
}关键提示: 在Next.js 15+中,是一个,必须使用await:
paramsPromisetypescript
type RouteParams = {
params: Promise<{ teamId: string }>;
};
export async function GET(req: NextRequest, { params }: RouteParams) {
const { teamId } = await params; // 必须await!
// ...
}Using withErrorHandler (Recommended)
使用withErrorHandler(推荐)
typescript
import { NextRequest, NextResponse } from "next/server";
import { requireTeamMember, parseJsonBody, withErrorHandler, ApiError } from "@/lib/api-helpers";
import { prisma } from "@/lib/prisma";
import { z } from "zod";
const createSchema = z.object({
name: z.string().min(1),
});
type RouteParams = {
params: Promise<{ teamId: string }>;
};
export const POST = withErrorHandler(async (req: NextRequest, { params }: RouteParams) => {
const { teamId } = await params;
// Auth check - throws 401/403
await requireTeamMember(teamId);
// Parse and validate body - throws 400
const { name } = await parseJsonBody(req, createSchema);
// Business logic
const result = await prisma.someModel.create({
data: { name, teamId },
});
return NextResponse.json(result, { status: 201 });
});typescript
import { NextRequest, NextResponse } from "next/server";
import { requireTeamMember, parseJsonBody, withErrorHandler, ApiError } from "@/lib/api-helpers";
import { prisma } from "@/lib/prisma";
import { z } from "zod";
const createSchema = z.object({
name: z.string().min(1),
});
type RouteParams = {
params: Promise<{ teamId: string }>;
};
export const POST = withErrorHandler(async (req: NextRequest, { params }: RouteParams) => {
const { teamId } = await params;
// 身份验证检查 - 抛出401/403错误
await requireTeamMember(teamId);
// 解析并校验请求体 - 抛出400错误
const { name } = await parseJsonBody(req, createSchema);
// 业务逻辑
const result = await prisma.someModel.create({
data: { name, teamId },
});
return NextResponse.json(result, { status: 201 });
});React Query Keys
React Query Keys
typescript
// Factory pattern for query keys
export const teamKeys = {
all: ["teams"] as const,
lists: () => [...teamKeys.all, "list"] as const,
details: () => [...teamKeys.all, "detail"] as const,
detail: (id: string) => [...teamKeys.details(), id] as const,
members: (id: string) => [...teamKeys.detail(id), "members"] as const,
};typescript
// 工厂模式生成查询键
export const teamKeys = {
all: ["teams"] as const,
lists: () => [...teamKeys.all, "list"] as const,
details: () => [...teamKeys.all, "detail"] as const,
detail: (id: string) => [...teamKeys.details(), id] as const,
members: (id: string) => [...teamKeys.detail(id), "members"] as const,
};Mutation Pattern (Simple Invalidation)
突变模式(简单失效)
typescript
export function useCreateTeam() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createTeam,
onSuccess: (newTeam) => {
queryClient.invalidateQueries({ queryKey: teamKeys.lists() });
queryClient.setQueryData(teamKeys.detail(newTeam.id), newTeam);
},
});
}typescript
export function useCreateTeam() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createTeam,
onSuccess: (newTeam) => {
queryClient.invalidateQueries({ queryKey: teamKeys.lists() });
queryClient.setQueryData(teamKeys.detail(newTeam.id), newTeam);
},
});
}Background Jobs
后台任务
pg-boss Setup
pg-boss配置
Jobs run alongside Next.js in the same container via startup script.
Queue pattern:
typescript
// Create job
await boss.send(QUEUES.SEND_EMAIL, payload, DEFAULT_JOB_OPTIONS);
// Worker handles job
boss.work(QUEUES.SEND_EMAIL, { batchSize: 1 }, async (job) => {
await handleSendEmail(job.data);
});任务与Next.js在同一容器中通过启动脚本运行。
队列模式:
typescript
// 创建任务
await boss.send(QUEUES.SEND_EMAIL, payload, DEFAULT_JOB_OPTIONS);
// 处理器处理任务
boss.work(QUEUES.SEND_EMAIL, { batchSize: 1 }, async (job) => {
await handleSendEmail(job.data);
});Scheduled Jobs
定时任务
typescript
// Schedule recurring jobs on worker startup
await boss.schedule(QUEUES.CLEANUP, "0 6 * * *", {}); // Daily at 6 AM UTCtypescript
// 处理器启动时设置重复任务
await boss.schedule(QUEUES.CLEANUP, "0 6 * * *", {}); // 每天UTC时间6点执行Deployment
部署
Dockerfile
Dockerfile
Single container running both Next.js server and pg-boss worker:
dockerfile
FROM node:22-alpine
RUN npm install -g bun单容器同时运行Next.js服务器和pg-boss处理器:
dockerfile
FROM node:22-alpine
RUN npm install -g bun... build steps ...
... 构建步骤 ...
CMD ["/app/start.sh"] # Runs both processes
undefinedCMD ["/app/start.sh"] # 同时运行两个进程
undefinedRailway Config
Railway配置
toml
[build]
builder = "dockerfile"
[deploy]
healthcheckPath = "/"
healthcheckTimeout = 300
restartPolicyType = "on_failure"toml
[build]
builder = "dockerfile"
[deploy]
healthcheckPath = "/"
healthcheckTimeout = 300
restartPolicyType = "on_failure"next.config.ts Rewrites
next.config.ts 重写规则
typescript
async rewrites() {
return [
{
source: "/uploads/:path*",
destination: "/api/uploads/:path*",
},
];
}typescript
async rewrites() {
return [
{
source: "/uploads/:path*",
destination: "/api/uploads/:path*",
},
];
}Checklist
检查清单
- Project initialized with Next.js + TypeScript
- Dependencies installed (see list above)
- Environment variables configured
- Prisma schema with User, Team, TeamMember, Invitation
- NextAuth v5 with Credentials provider
- Dashboard layout with server-side auth check
- API helpers (withErrorHandler, requireTeamMember, etc.)
- proxy.ts for security headers
- Team creation and switching
- Member invitation flow
- Light/dark mode toggle
- React Query configured
- Resend email service
- S3 storage with /uploads proxy
- pg-boss for background jobs
- Dockerfile for Railway deployment
- 使用Next.js + TypeScript初始化项目
- 安装所有依赖(见上方列表)
- 配置环境变量
- 包含User、Team、TeamMember、Invitation的Prisma Schema
- 带有Credentials提供者的NextAuth v5
- 包含服务端身份验证检查的Dashboard布局
- API工具类(withErrorHandler、requireTeamMember等)
- 用于安全头的proxy.ts
- 团队创建与切换功能
- 成员邀请流程
- 明暗模式切换器
- 配置React Query
- Resend邮件服务
- 带有/uploads代理的S3存储
- 用于后台任务的pg-boss
- 用于Railway部署的Dockerfile