基于Nx Monorepo与Supabase构建AI编程规则管理平台
1. 项目概述:一个为AI编程助手打造的规则管理平台
如果你和我一样,日常重度依赖Cursor这类AI编程工具,那你肯定也遇到过类似的困扰:每次新建项目,都得重新给AI解释一遍代码规范、项目结构、命名约定,甚至是一些特定的业务逻辑。这个过程不仅重复,而且容易遗漏,导致AI生成的代码风格不一,后期还得花时间手动调整。CursorRulesCraft这个开源项目,就是为了解决这个痛点而生的。
简单来说,这是一个基于现代Web技术栈(React 19, TypeScript, Vite, Supabase)构建的Web应用。它的核心功能是让你能在一个可视化的界面里,创建、管理和分享专属于你的“Cursor规则集”。你可以把这些规则集想象成给AI编程助手定制的“操作手册”或“公司规范”,里面可以包含项目结构说明、代码风格指南、API调用示例、甚至是常用的代码片段模板。一旦定义好,你就可以在Cursor中轻松引用,让AI从一开始就按照你的规矩来写代码,极大提升协作效率和代码一致性。
这个项目非常适合前端开发者、全栈工程师以及任何希望将AI编程工作流标准化的团队。它本身也是一个绝佳的学习案例,集成了当前最热门的技术栈,包括Nx Monorepo管理、Radix UI组件库、TanStack Query进行状态管理,以及Supabase作为全栈后端。接下来,我将带你从零开始,深入拆解这个项目的设计思路、技术实现和部署细节。
2. 核心架构与设计哲学解析
2.1 为什么选择Monorepo与统一环境变量?
项目一上手,最引人注目的就是其清晰的Nx Monorepo结构。将前端(React应用)、后端(NestJS API)和共享类型定义(shared-types)放在同一个代码仓库中,这背后有几个关键的考量。
首先,开发体验的一致性。在开发一个功能时,如果涉及前后端联调,在Monorepo中你可以同时修改两边的代码,并立即看到效果,无需在多个仓库间切换、提交、拉取。这对于需要快速迭代的AI工具类产品至关重要。其次,共享代码的便利性。packages/shared-types这个包专门存放前后端共用的TypeScript类型定义(比如用户、工作区、仓库的数据结构)。这意味着,当你修改了一个API接口的返回类型时,前端的类型定义会自动同步更新,彻底杜绝了前后端类型不一致导致的运行时错误。
另一个设计亮点是统一的.env文件管理。传统的多应用项目往往在每个子目录(如apps/frontend/,apps/backend/)下都有自己的.env文件,管理起来非常混乱,容易遗漏。CursorRulesCraft强制规定,所有环境变量都集中存放在项目根目录的.env文件中。前端(Vite)通过配置envDir: ‘../../‘来读取,后端(NestJS)通过envFilePath: ‘../../.env’来读取。NX工具链也会将这个.env文件标记为“共享全局输入”,确保任何环境变量的变更都能触发所有相关应用的重新构建。
注意:这种设计虽然清晰,但也带来了一个常见的“坑”。新手在克隆项目后,常常会习惯性地在
apps/frontend或apps/backend目录下创建.env文件,这会导致应用读取不到正确的配置而启动失败。务必记住,.env文件有且仅有一个,就在项目根目录。
2.2 技术栈选型的深度考量
项目的技术栈堪称“现代前端全家桶”,每一个选择都经过了深思熟虑。
- React 19 + TypeScript + Vite:这是当前构建高性能、类型安全前端应用的事实标准组合。Vite的极速热更新(HMR)对于需要频繁调整UI的规则编辑器来说,体验提升是巨大的。TypeScript则确保了在定义复杂规则数据结构时的类型安全。
- Tailwind CSS + Radix UI:这是一个“样式与逻辑分离”的典范。Radix UI提供了一系列完全无样式、但具备完备无障碍访问性(a11y)和键盘交互逻辑的底层UI原语(如Dialog、Dropdown)。开发者在此基础上,用Tailwind CSS进行完全自由的样式定制。这既保证了UI组件的可访问性和交互质量,又避免了被特定UI库的样式所束缚,非常适合需要独特品牌设计的产品。
- TanStack Query (React Query):在管理服务器状态(如从Supabase获取规则列表、用户信息)时,它几乎是不二之选。它自动处理了缓存、后台刷新、请求去重、错误重试等复杂逻辑。在这个项目中,查询规则、更新规则等操作都通过TanStack Query来管理,代码简洁且健壮。
- Supabase:选择Supabase作为BaaS(后端即服务)是项目的关键决策。它不仅仅是一个PostgreSQL数据库,更集成了实时订阅、行级安全策略(RLS)、存储和身份验证。对于这样一个以数据管理为核心的工具,使用Supabase可以让我们免于搭建复杂的后端用户系统和实时同步逻辑,专注于业务开发。其与PostgreSQL深度集成的RLS功能,能非常优雅地实现“用户只能访问自己工作区数据”这类权限需求。
3. 从零开始的完整环境搭建与配置实操
3.1 前置工具链的安装与版本管理
项目明确要求Node.js版本为v22或更高。我强烈推荐使用nvm(Node Version Manager)或fnm来管理Node版本,这能避免全局版本冲突。
# 使用nvm安装并切换至Node.js 22 nvm install 22 nvm use 22 # 验证版本 node --version # 应输出 v22.x.x接下来安装Bun。Bun在这个项目中不仅是包管理器(替代npm/yarn),其内置的测试运行器、打包工具和快速的安装速度也能提升整体开发体验。
# 安装Bun (macOS/Linux) curl -fsSL https://bun.sh/install | bash # 安装后重启终端,或按照提示将Bun添加到PATH bun --version最后是Docker和Supabase CLI。Docker用于运行本地Supabase服务(一个完整的PostgreSQL数据库+管理界面),而Supabase CLI则是管理数据库迁移、启动服务的命令行工具。
# 安装Docker: 请根据你的操作系统访问 https://docs.docker.com/get-docker/ # 安装Supabase CLI # macOS brew install supabase/tap/supabase # 其他平台可通过npm安装 npm install -g supabase3.2 项目初始化与本地Supabase启动
克隆项目并安装依赖后,第一步不是启动应用,而是启动本地Supabase服务。这是整个项目的基石,因为前端和后端都依赖它提供的数据库和API。
git clone https://github.com/PhongLee1210/CursorRulesCraft.git cd CursorRulesCraft bun install # 关键步骤:启动本地Supabase supabase start执行supabase start后,终端会输出一系列关键信息,务必保存好:
Started supabase local development setup. API URL: http://127.0.0.1:54321 GraphQL URL: http://127.0.0.1:54321/graphql/v1 DB URL: postgresql://postgres:postgres@127.0.0.1:54322/postgres Studio URL: http://127.0.0.1:54323 Inbucket URL: http://127.0.0.1:54324 JWT secret: super-secret-jwt-token anon key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...(很长一串) service_role key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...(很长一串)这里的API URL、anon key和service_role key是后续配置环境变量的核心。
3.3 环境变量配置的“避坑指南”
接下来创建项目根目录下的.env文件。项目提供了env.example模板,直接复制并修改是最稳妥的方式。
cp env.example .env # 然后用编辑器打开 .env 文件进行配置根据supabase start的输出,你需要填充以下关键部分:
# Supabase 配置(使用上面输出的值) SUPABASE_URL=http://127.0.0.1:54321 SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... # 粘贴你的 anon key SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... # 粘贴你的 service_role key # Clerk 身份验证(需要去 https://dashboard.clerk.com 创建应用获取) CLERK_SECRET_KEY=sk_test_... VITE_CLERK_PUBLISHABLE_KEY=pk_test_... # 前端配置(Vite要求变量以VITE_开头才能被客户端访问) VITE_API_URL=http://localhost:4000 VITE_SUPABASE_URL=http://127.0.0.1:54321这里有几个极易出错的点:
- 文件位置:再次强调,
.env文件必须在项目根目录,而不是任何子文件夹内。 - 变量前缀:所有需要在浏览器中访问的变量(比如Supabase的客户端密钥),其变量名必须以
VITE_开头,否则Vite在构建时会将其剥离,前端代码将无法读取。 - Clerk密钥:
CLERK_SECRET_KEY用于后端验证,而VITE_CLERK_PUBLISHABLE_KEY用于前端初始化Clerk客户端。不要混淆,也不要错误地在前端暴露了Secret Key。
配置完成后,运行bun run dev:all,你应该能同时看到前端(localhost:3000)和后端(localhost:4000)成功启动,并且可以通过Supabase Studio(localhost:54323)管理你的本地数据库了。
4. 核心功能模块的实现与代码剖析
4.1 基于Radix UI与Tailwind的可复用组件库构建
项目在apps/frontend/src/components目录下构建了一套高质量的UI组件。以Button组件为例,它完美体现了“基于Radix原语进行样式封装”的最佳实践。
// 简化版的 Button 组件实现思路 import * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "@/lib/utils"; // 一个合并Tailwind class的工具函数 // 使用class-variance-authority (cva) 定义按钮的所有变体 const buttonVariants = cva( "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, size: { default: "h-10 px-4 py-2", sm: "h-9 rounded-md px-3", lg: "h-11 rounded-md px-8", icon: "h-10 w-10", }, }, defaultVariants: { variant: "default", size: "default", }, } ); export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> { asChild?: boolean; } const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( ({ className, variant, size, asChild = false, ...props }, ref) => { const Comp = asChild ? Slot : "button"; return ( <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} /> ); } ); Button.displayName = "Button"; export { Button, buttonVariants };设计解析:
cva函数:用于声明组件的所有样式变体(variant)和尺寸(size)。这种方式比手动拼接className字符串更类型安全、更易于维护。cn工具函数:这是tailwind-merge和clsx的封装,用于智能合并和去重Tailwind类名,避免样式冲突。asChild属性:这是Radix UI的一个高级模式。当asChild={true}时,Button会将所有属性传递给其唯一的子元素,而不是渲染一个<button>标签。这在你需要将一个<Link>组件(来自React Router)渲染成按钮样式时极其有用,能保证语义正确性和无障碍性。forwardRef:允许父组件直接获取底层DOM元素的引用,这是构建可复用组件库的标配。
通过这种方式构建的组件库,既保证了设计的一致性,又提供了极大的灵活性。开发者可以通过组合variant和size属性,快速获得各种样式的按钮,而无需编写一行CSS。
4.2 使用TanStack Query与Supabase进行数据同步
前端与Supabase的交互主要通过@supabase/supabase-js客户端库,并结合TanStack Query进行状态管理。以下是一个典型的“获取规则列表”的示例:
// 在 hooks/useRules.ts 中 import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { supabase } from '@/lib/supabase-client'; import type { Database } from '@shared-types/database'; // 从共享包导入类型 // 定义查询Key,用于缓存和失效 const RULES_QUERY_KEY = ['rules']; // 获取当前用户所有规则的Hook export function useRules(workspaceId: string) { return useQuery({ queryKey: [...RULES_QUERY_KEY, workspaceId], queryFn: async () => { // 使用Supabase客户端进行查询,并启用RLS const { data, error } = await supabase .from('rules') .select('*') .eq('workspace_id', workspaceId) .order('created_at', { ascending: false }); if (error) { throw new Error(`Failed to fetch rules: ${error.message}`); } return data; }, enabled: !!workspaceId, // 只有workspaceId存在时才启用查询 staleTime: 5 * 60 * 1000, // 数据在5分钟内被认为是新鲜的,不会重新获取 }); } // 创建新规则的Mutation Hook export function useCreateRule() { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (newRule: Omit<Database['public']['Tables']['rules']['Insert'], 'id'>) => { const { data, error } = await supabase .from('rules') .insert([newRule]) .select() .single(); if (error) throw error; return data; }, onSuccess: (newRule) => { // 规则创建成功后,使旧的规则列表缓存失效,触发重新获取 queryClient.invalidateQueries({ queryKey: RULES_QUERY_KEY }); // 或者,更高效地,直接更新缓存(乐观更新) // queryClient.setQueryData([...RULES_QUERY_KEY, newRule.workspace_id], (old) => [newRule, ...(old || [])]); }, }); }最佳实践解读:
- 查询键(Query Key):
[‘rules’]是根键,加上workspaceId构成一个唯一的缓存标识。这确保了不同工作区的规则数据独立缓存。 - 启用条件(enabled):
enabled: !!workspaceId是一个重要模式。它防止在workspaceId为空或未加载时发起无意义的网络请求。 - 过期时间(staleTime):设置为5分钟,意味着在这段时间内,如果用户再次访问同一页面,TanStack Query会直接返回缓存的数据,而不会发起新的请求,提升了用户体验。
- 缓存失效(Invalidation):在
useCreateRule的onSuccess回调中,我们调用invalidateQueries使所有RULES_QUERY_KEY相关的缓存失效。下次组件需要规则数据时,它会自动重新从服务器获取,保证数据一致性。对于更极致的用户体验,可以采用注释中的“乐观更新”策略,在请求发出前就立即更新UI,请求成功后再修正或请求失败后回滚。
4.3 数据库设计与行级安全策略(RLS)配置
项目的核心数据模型围绕workspaces(工作区)、members(成员)和rules(规则)展开。Supabase的RLS使得权限管理变得清晰。以下是rules表的迁移文件示例:
-- supabase/migrations/002_create_rules.sql create table public.rules ( id uuid default gen_random_uuid() primary key, workspace_id uuid not null references public.workspaces(id) on delete cascade, name text not null, content text not null, -- 存储规则的具体内容,可能是JSON或Markdown description text, is_public boolean default false, created_by uuid references auth.users(id), created_at timestamp with time zone default timezone('utc'::text, now()) not null, updated_at timestamp with time zone default timezone('utc'::text, now()) not null ); -- 启用RLS alter table public.rules enable row level security; -- 创建策略:用户只能查看自己所在工作区的规则,或者公开的规则 create policy "Users can view rules in their workspace or public rules" on public.rules for select using ( -- 条件1:规则属于用户所在的工作区 workspace_id in ( select workspace_id from public.members where user_id = auth.uid() ) or -- 条件2:规则是公开的 is_public = true ); -- 创建策略:用户只能在自己所在的工作区创建、更新、删除规则 create policy "Users can manage rules in their workspace" on public.rules for all -- 包括 INSERT, UPDATE, DELETE using ( workspace_id in ( select workspace_id from public.members where user_id = auth.uid() ) ); with check ( -- with check 确保新插入或更新的行也满足条件 workspace_id in ( select workspace_id from public.members where user_id = auth.uid() ) ); -- 创建索引以提升查询性能 create index idx_rules_workspace_id on public.rules(workspace_id); create index idx_rules_is_public on public.rules(is_public);RLS策略精讲:
for select策略控制读取权限。这里允许用户查看两种规则:1) 他们所属工作区的所有规则;2) 所有标记为公开(is_public = true)的规则。这为未来实现“规则市场”或模板分享功能打下了基础。for all策略结合using和with check子句,控制所有写操作。它确保用户只能对workspace_id属于自己成员身份的记录进行操作。with check子句尤其重要,它防止用户通过API修改workspace_id来越权操作其他工作区的数据。- 性能考虑:最后的索引创建至关重要。在
workspace_id和is_public字段上创建索引,能极大加速根据工作区筛选和查找公开规则的查询速度。
这种基于RLS的权限模型,将数据访问控制完全下放到数据库层,后端API服务几乎不需要编写额外的权限校验代码,只需依赖Supabase返回的auth.uid()(当前登录用户的ID)即可,极大地简化了后端逻辑。
5. 生产环境部署与持续集成实战
5.1 使用Docker进行容器化部署
项目提供了灵活的Docker部署方案,支持“单体服务”和“前后端分离”两种模式。生产环境推荐使用“单体服务”模式,它将构建好的前端静态文件与Node.js后端服务打包进同一个容器,简化了部署和网络配置。
关键的Dockerfile结构如下:
# 多阶段构建:阶段1 - 构建前端 FROM node:22-alpine AS frontend-builder WORKDIR /app COPY apps/frontend/package.json apps/frontend/bun.lockb ./ RUN bun install --frozen-lockfile COPY apps/frontend/ . # 通过构建参数传入前端环境变量 ARG VITE_API_URL ARG VITE_CLERK_PUBLISHABLE_KEY ENV VITE_API_URL=${VITE_API_URL} ENV VITE_CLERK_PUBLISHABLE_KEY=${VITE_CLERK_PUBLISHABLE_KEY} RUN bun run build # 多阶段构建:阶段2 - 构建后端 FROM node:22-alpine AS backend-builder WORKDIR /app COPY apps/backend/package.json apps/backend/bun.lockb ./ RUN bun install --frozen-lockfile --production=false COPY apps/backend/ . RUN bun run build # 多阶段构建:阶段3 - 组合成生产镜像 FROM node:22-alpine AS combined-production WORKDIR /app # 安装仅生产环境依赖 COPY apps/backend/package.json apps/backend/bun.lockb ./ RUN bun install --production --frozen-lockfile # 从构建阶段复制产物 COPY --from=backend-builder /app/dist ./dist COPY --from=frontend-builder /app/dist ./public # 健康检查 HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ CMD node -e "require('http').get('http://localhost:${PORT:-4000}/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" EXPOSE 4000 CMD ["node", "dist/main.js"]部署要点:
- 多阶段构建:显著减小了最终生产镜像的体积。
frontend-builder和backend-builder阶段安装了完整的开发依赖用于构建,而最终的combined-production阶段只安装后端运行所必需的生产依赖。 - 构建参数(ARG):前端构建时需要
VITE_开头的环境变量。在Docker构建时通过--build-arg传入,并在构建阶段设置为环境变量,这样Vite才能在构建过程中将其替换到客户端代码中。 - 健康检查(HEALTHCHECK):这是生产级容器的重要配置。它让容器编排平台(如Docker Compose、Kubernetes、Render)能够感知服务是否健康运行,并自动重启不健康的实例。
5.2 部署到Render.com的完整流程
Render是一个对开发者友好的PaaS平台,非常适合部署此类全栈应用。以下是部署步骤:
- 在Render创建新的Web Service,选择“从Dockerfile部署”。
- 配置构建命令:
- 构建命令:
docker build --target combined --build-arg VITE_API_URL=https://your-app.onrender.com --build-arg VITE_CLERK_PUBLISHABLE_KEY=$VITE_CLERK_PUBLISHABLE_KEY . - 注意:
VITE_API_URL需要设置为你的Render服务最终域名,因为前端需要知道后端API的地址。$VITE_CLERK_PUBLISHABLE_KEY则会引用你在Render面板上设置的环境变量。
- 构建命令:
- 配置启动命令:
node dist/main.js - 设置端口:
4000 - 添加环境变量:在Render的Environment面板中,添加所有
.env文件中需要的生产环境变量,特别是Supabase的生产环境密钥(从Supabase项目设置中获取)和Clerk的生产密钥。
重要提示:在部署前,务必使用Supabase CLI将本地数据库迁移到生产环境。在项目根目录执行:
supabase login supabase link --project-ref your-project-ref # 在Supabase项目设置中找到 supabase db push此操作会将
supabase/migrations/下的所有迁移脚本应用到生产数据库。务必先在本地测试,并备份生产数据。
5.3 GitHub Actions自动化工作流解析
项目根目录下的.github/workflows/build-and-quality.yml定义了一个完整的CI/CD流水线,它会在每次推送到主分支或发起Pull Request时自动运行。
name: Build & Quality Check on: [push, pull_request] jobs: quality: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v1 with: { bun-version: 'latest' } - run: bun install - run: bun run lint # 运行ESLint检查代码风格 - run: bun run type-check # 运行TypeScript类型检查 - run: bun run format:check # 使用Prettier检查代码格式 - run: bun run test # 运行测试套件 build: runs-on: ubuntu-latest needs: quality # 只有quality job通过后才运行build steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v1 with: { bun-version: 'latest' } - run: bun install - run: bun run build:frontend - run: bun run build:backend这个工作流确保了代码库的质量:
- 并行与依赖:
quality和build是两个job。buildjob通过needs: quality指定了依赖关系,只有代码检查全部通过后,才会尝试构建,避免了在存在类型错误或格式问题时浪费资源进行构建。 - 检查项全面:
lint:使用ESLint捕捉潜在的错误代码模式和风格问题。type-check:这是TypeScript项目的核心,确保类型安全,能在编译前发现接口不匹配等错误。format:check:使用Prettier确保代码风格统一,避免因格式问题产生的无意义代码差异。test:运行单元测试和集成测试,保证核心功能的正确性。
- 使用Bun:工作流也使用了Bun作为运行时,与本地开发环境保持一致,确保了“在本地能跑,在CI上也能跑”。
6. 开发与生产中的常见问题排查实录
在实际开发和部署过程中,我遇到并总结了一些典型问题及其解决方案。
6.1 环境变量导致的启动失败
问题现象:运行bun run dev:backend时,后端服务崩溃,报错Error: Missing required environment variables: SUPABASE_URL。
排查步骤:
- 确认文件存在与位置:首先检查项目根目录下是否存在
.env文件。ls -la .env。 - 检查变量名:确认
.env文件中变量名拼写正确,特别是SUPABASE_URL、SUPABASE_ANON_KEY等,是否与后端代码中process.env.SUPABASE_URL读取的键名完全一致。 - 检查Supabase服务状态:运行
supabase status,确保本地Supabase实例正在运行。如果未运行,执行supabase start。 - 重启开发服务器:有时环境变量在服务启动后才被加载,需要重启后端服务。
根本原因:绝大多数情况下,这是因为.env文件缺失,或者变量名错误(例如写成了SUPBASE_URL),或者Supabase服务未启动,导致后端无法连接到数据库。
6.2 数据库连接与迁移问题
问题现象:应用启动后,前端能打开但无法加载数据,或后端API返回数据库错误。
排查步骤:
- 验证数据库连接:使用
supabase status查看数据库URL,尝试用psql或图形化工具(如TablePlus)直接连接,检查网络和认证。 - 检查迁移状态:运行
supabase db reset(注意:这会清空本地数据)或supabase migration list查看所有迁移是否已应用。 - 查看Supabase日志:在另一个终端运行
supabase logs,查看数据库操作的详细日志,寻找错误信息。 - 检查RLS策略:如果错误提示“权限被拒绝”,很可能是RLS策略在作祟。通过Supabase Studio的SQL编辑器,以
service_role身份(拥有最高权限)执行select * from auth.users;和select * from public.members;,确认测试用户是否存在且已关联到正确的工作区。
实操心得:在开发初期,可以暂时在Supabase Studio中手动禁用某个表的RLS(alter table public.rules disable row level security;)来快速判断问题是出在RLS策略还是查询语句本身。但切记在测试完成后重新启用RLS,并完善策略。
6.3 前端构建与资源加载错误
问题现象:本地开发正常,但生产构建后访问页面出现白屏或资源加载失败(404)。
排查步骤:
- 检查构建输出:运行
bun run build:frontend后,查看apps/frontend/dist目录下的index.html。检查其中引用的JS和CSS文件路径是否正确。在单容器部署中,前端资源是作为后端的静态文件服务的,路径配置是关键。 - 验证基础路径(Base URL):在
vite.config.ts中,检查base配置。对于部署在子路径下的情况(如https://example.com/app/),需要设置为base: ‘/app/‘。在Render等根域名部署时,通常为base: ‘/‘。 - 检查环境变量替换:生产构建时,确保以
VITE_开头的环境变量已正确通过构建参数传入。可以检查构建出的JS文件,搜索import.meta.env.VITE_API_URL,看是否已被替换为具体的URL值,而不是空字符串。 - 查看浏览器开发者工具:打开Network和Console标签页,查看具体的404错误是哪个文件,以及是否有JavaScript运行时错误。
一个典型坑点:在Docker构建时,如果--build-arg传递的环境变量值包含特殊字符(如换行符的私钥),需要用引号包裹,并在Dockerfile中妥善处理。对于多行值,可以考虑先将内容存入一个文件,然后在构建时通过COPY指令传入。
6.4 性能优化与监控建议
当项目上线后,随着用户和规则数量增长,以下几点优化可以显著提升体验:
数据库查询优化:
- 为常用查询字段添加索引:如
rules(workspace_id, created_at)。这能大幅加速“获取某个工作区最新规则”这类查询。 - 避免
select *:在TanStack Query的查询函数中,明确指定需要的字段,如select(‘id, name, description, updated_at’),减少不必要的数据传输。 - 使用分页:当规则列表很长时,在Supabase查询中使用
.range(start, end)实现分页,避免一次性拉取过多数据。
- 为常用查询字段添加索引:如
前端资源优化:
- 代码分割:利用Vite/React Router的懒加载(
React.lazy),将不同页面拆分成独立的chunk,实现按需加载。 - 图片等静态资源优化:如果规则内容包含图片,建议上传至Supabase Storage并启用图片压缩和CDN。
- 代码分割:利用Vite/React Router的懒加载(
实施基础监控:
- 错误追踪:集成Sentry或类似的错误监控服务,捕获前端和后端的运行时错误。
- 性能监控:使用Lighthouse进行定期性能审计,关注核心Web指标(LCP, FID, CLS)。
- 日志聚合:确保后端(NestJS)的日志被妥善收集,可以使用
pino等日志库,并输出为JSON格式,便于被Logtail、Datadog等平台摄取分析。
这个项目从技术选型到工程实践,都体现了一个现代、健壮的全栈应用应有的样子。它不仅解决了AI编程规则管理的实际问题,其代码本身也是一个高质量的学习范本。无论是想学习Monorepo管理、Supabase全栈开发,还是想构建自己的生产级SaaS应用,深入研究和实践CursorRulesCraft的代码,都会让你受益匪浅。
