team-saas

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Team 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

技术栈概览

LayerTechnologyVersion
FrameworkNext.js (App Router)16.x
ReactReact19.x
RuntimeBunLatest
DatabasePostgreSQL (Railway)-
ORMPrisma7.x
AuthNextAuth v5 (Auth.js)5.0.0-beta
UIshadcn/ui (new-york)Latest
StylingTailwind CSSv4
Themingnext-themesLatest
IconsLucide ReactLatest
StateTanStack React Query5.x
ValidationZod4.x
EmailResend6.x
StorageRailway S3-compatible-
Jobspg-boss12.x
HostingRailway-
层级技术栈版本
框架Next.js (App Router)16.x
ReactReact19.x
运行时BunLatest
数据库PostgreSQL (Railway)-
ORMPrisma7.x
身份验证NextAuth v5 (Auth.js)5.0.0-beta
UI组件库shadcn/ui (new-york)Latest
样式Tailwind CSSv4
主题next-themesLatest
图标Lucide ReactLatest
状态管理TanStack React Query5.x
校验Zod4.x
邮件服务Resend6.x
存储Railway S3-compatible-
任务队列pg-boss12.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 routes
src/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 output
src/
├── 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:
AssetDescription
assets/lib/auth.ts
NextAuth v5 with Credentials provider
assets/lib/auth.config.ts
Auth config (callbacks, custom pages)
assets/lib/api-helpers.ts
withErrorHandler, requireTeamMember, ApiError
assets/lib/prisma.ts
Lazy-loaded Prisma client with pg adapter
assets/lib/s3.ts
S3 utilities with presigned URLs
assets/lib/resend.ts
Resend email client + templates
assets/lib/query-client.ts
React Query setup
assets/lib/boss.ts
pg-boss job queue
assets/lib/utils.ts
Utility functions
assets/components/providers.tsx
App providers
assets/components/theme-toggle.tsx
Light/dark/system toggle
assets/components/team-switcher.tsx
Team dropdown
assets/config/globals.css
Linear-style design system
assets/config/Dockerfile
Production Docker build
assets/config/railway.toml
Railway deployment
assets/config/next.config.ts
Next.js configuration
assets/config/proxy.ts
Security headers
assets/config/.env.example
Environment variables
assets/config/components.json
shadcn configuration
assets/prisma/schema.prisma
Base team schema
assets/api/teams-route.ts
Team API template
assets/api/invitations-route.ts
Invitations API template
assets/hooks/use-teams.ts
React Query hooks
assets/types/next-auth.d.ts
Type extensions
此Skill包含可直接使用的模板:
资源文件描述
assets/lib/auth.ts
带有Credentials提供者的NextAuth v5配置
assets/lib/auth.config.ts
身份验证配置(回调、自定义页面)
assets/lib/api-helpers.ts
withErrorHandler、requireTeamMember、ApiError
assets/lib/prisma.ts
带有pg适配器的懒加载Prisma客户端
assets/lib/s3.ts
带有预签名URL的S3工具类
assets/lib/resend.ts
Resend邮件客户端 + 模板
assets/lib/query-client.ts
React Query配置
assets/lib/boss.ts
pg-boss任务队列
assets/lib/utils.ts
工具函数
assets/components/providers.tsx
应用提供者
assets/components/theme-toggle.tsx
明暗/系统主题切换器
assets/components/team-switcher.tsx
团队下拉选择器
assets/config/globals.css
Linear风格设计系统
assets/config/Dockerfile
生产环境Docker构建文件
assets/config/railway.toml
Railway部署配置
assets/config/next.config.ts
Next.js配置
assets/config/proxy.ts
安全头配置
assets/config/.env.example
环境变量示例
assets/config/components.json
shadcn配置
assets/prisma/schema.prisma
基础团队Schema
assets/api/teams-route.ts
团队API模板
assets/api/invitations-route.ts
邀请API模板
assets/hooks/use-teams.ts
React Query hooks
assets/types/next-auth.d.ts
类型扩展

Setup Instructions

安装说明

1. Initialize Project

1. 初始化项目

bash
bunx create-next-app@latest my-saas --typescript --tailwind --eslint --app --src-dir
cd my-saas
bash
bunx create-next-app@latest my-saas --typescript --tailwind --eslint --app --src-dir
cd my-saas

2. Install Dependencies

2. 安装依赖

bash
undefined
bash
undefined

Core

核心依赖

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

Email

邮件服务

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
undefined
bunx shadcn@latest init bunx shadcn@latest add button card dialog input label sonner tooltip dropdown-menu avatar badge form command popover
undefined

3. Configure Environment

3. 配置环境变量

Copy
.env.example
and fill in values:
bash
undefined
复制
.env.example
并填写对应值:
bash
undefined

Required

必填项

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"
undefined
AWS_ENDPOINT_URL="https://..." AWS_ACCESS_KEY_ID="..." AWS_SECRET_ACCESS_KEY="..." AWS_S3_BUCKET_NAME="..." AWS_DEFAULT_REGION="auto"
undefined

4. Setup Prisma

4. 配置Prisma

bash
undefined
bash
undefined

Initialize

初始化

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
undefined
bunx prisma db push bunx prisma generate
undefined

5. Copy Asset Files

5. 复制资源文件

Copy the template files from
assets/
to your project, adjusting imports as needed.
assets/
下的模板文件复制到你的项目中,按需调整导入路径。

Design System

设计系统

Brand Colors

品牌颜色

Primary purple:
#8b5cf6
(HSL: 262 83% 58%)
主紫色:
#8b5cf6
(HSL: 262 83% 58%)

Light 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:
    #0A0A0B
    (hsl 240 5% 4%)
  • Foreground:
    hsl(0 0% 95%)
  • Border:
    rgba(255,255,255,0.06)
  • 背景色:
    #0A0A0B
    (hsl 240 5% 4%)
  • 前景色:
    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
next-themes
with
attribute="class"
.
仅含图标的三选项分段控件:
  • 太阳图标 → 亮色模式
  • 月亮图标 → 暗色模式
  • 显示器图标 → 系统模式
位于侧边栏底部或顶部。使用
next-themes
,配置
attribute="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+,
params
is a
Promise
and MUST be awaited:
typescript
type RouteParams = {
  params: Promise<{ teamId: string }>;
};

export async function GET(req: NextRequest, { params }: RouteParams) {
  const { teamId } = await params;  // MUST await!
  // ...
}
关键提示: 在Next.js 15+中,
params
是一个
Promise
,必须使用await:
typescript
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 UTC
typescript
// 处理器启动时设置重复任务
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
undefined
CMD ["/app/start.sh"] # 同时运行两个进程
undefined

Railway 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