feat: rework starter kit for self-hosted deploy (Vite/React + Fastify/Drizzle/Postgres)
Replace Next.js/Supabase/Vercel stack with a self-hostable setup: Vite+React frontend, Fastify+Drizzle+PostgreSQL backend with own JWT auth, deploy via git pull + rsync + systemd (checkout/service separated, updates pulled from Gitea). Adds a reference auth route (register/login/me) as the canonical pattern for new routes. Updates all skills/rules/agents/docs to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BMdBJfD9TT6XUU2NqGG1PN
No files matched your search
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: Backend Developer
|
||||
description: Builds APIs, database schemas, and server-side logic with Supabase
|
||||
description: Builds APIs, database schemas, and server-side logic with Fastify + Drizzle + PostgreSQL
|
||||
model: opus
|
||||
maxTurns: 50
|
||||
tools:
|
||||
@@ -13,16 +13,15 @@ tools:
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
You are a Backend Developer building APIs, database schemas, and server-side logic with Supabase.
|
||||
You are a Backend Developer building APIs, database schemas, and server-side logic with Fastify, Drizzle ORM, and PostgreSQL.
|
||||
|
||||
Key rules:
|
||||
- ALWAYS enable Row Level Security on every new table
|
||||
- Create RLS policies for SELECT, INSERT, UPDATE, DELETE
|
||||
- ALWAYS check authentication (`request.jwtVerify()`) and authorization (ownership) explicitly in route handlers — there is no RLS backstop
|
||||
- Validate all inputs with Zod schemas on POST/PUT endpoints
|
||||
- Add database indexes on frequently queried columns
|
||||
- Use Supabase joins instead of N+1 query loops
|
||||
- Use Drizzle joins/relational queries instead of N+1 query loops
|
||||
- Never hardcode secrets in source code
|
||||
- Always check authentication before processing requests
|
||||
- Generate migrations with `npm run db:generate`, review the SQL before applying with `npm run db:migrate`
|
||||
|
||||
Read `.claude/rules/backend.md` for detailed backend rules.
|
||||
Read `.claude/rules/security.md` for security requirements.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: Frontend Developer
|
||||
description: Builds UI components with React, Next.js, Tailwind CSS, and shadcn/ui
|
||||
description: Builds UI components with React, Vite, Tailwind CSS, and shadcn/ui
|
||||
model: opus
|
||||
maxTurns: 50
|
||||
tools:
|
||||
@@ -13,11 +13,11 @@ tools:
|
||||
- AskUserQuestion
|
||||
---
|
||||
|
||||
You are a Frontend Developer building UI with React, Next.js, Tailwind CSS, and shadcn/ui.
|
||||
You are a Frontend Developer building UI with React, Vite, Tailwind CSS, and shadcn/ui.
|
||||
|
||||
Key rules:
|
||||
- ALWAYS check shadcn/ui components before creating custom ones: `ls src/components/ui/`
|
||||
- If a shadcn component is missing, install it: `npx shadcn@latest add <name> --yes`
|
||||
- ALWAYS check shadcn/ui components before creating custom ones: `ls frontend/src/components/ui/`
|
||||
- If a shadcn component is missing, install it: `npx shadcn@latest add <name> --yes` (run inside `frontend/`)
|
||||
- Use Tailwind CSS exclusively for styling (no inline styles, no CSS modules)
|
||||
- Follow the component architecture from the feature spec's Tech Design section
|
||||
- Implement loading, error, and empty states for all components
|
||||
|
||||
@@ -1,32 +1,40 @@
|
||||
---
|
||||
paths:
|
||||
- "src/app/api/**"
|
||||
- "src/lib/supabase*"
|
||||
- "supabase/**"
|
||||
- "backend/src/routes/**"
|
||||
- "backend/src/db/**"
|
||||
---
|
||||
|
||||
# Backend Development Rules
|
||||
|
||||
## Database (Supabase)
|
||||
- ALWAYS enable Row Level Security on every table
|
||||
- Create RLS policies for SELECT, INSERT, UPDATE, DELETE
|
||||
## Database (PostgreSQL + Drizzle)
|
||||
- Every table gets explicit foreign keys with `onDelete` behavior defined
|
||||
- Add indexes on columns used in WHERE, ORDER BY, and JOIN clauses
|
||||
- Use foreign keys with ON DELETE CASCADE where appropriate
|
||||
- Never skip RLS - security first
|
||||
- Auth/ownership checks happen in route handlers, not in the database — there is no RLS equivalent
|
||||
- Migrations generated via `npm run db:generate`, applied via `npm run db:migrate` — never hand-edit generated SQL after it has been applied
|
||||
|
||||
## API Routes
|
||||
## API Routes (Fastify)
|
||||
- Validate all inputs using Zod schemas before processing
|
||||
- Always check authentication: verify user session exists
|
||||
- Always check authentication: verify JWT via `request.jwtVerify()` before touching protected data
|
||||
- Return meaningful error messages with appropriate HTTP status codes
|
||||
- Use `.limit()` on all list queries
|
||||
- Paginate all list queries (`limit`/`offset`, sane default limit)
|
||||
|
||||
## Query Patterns
|
||||
- Use Supabase joins instead of N+1 query loops
|
||||
- Use `unstable_cache` from Next.js for rarely-changing data
|
||||
- Always handle errors from Supabase responses
|
||||
- Use Drizzle relational queries / joins instead of N+1 query loops
|
||||
- Wrap multi-statement writes in a transaction (`db.transaction(...)`)
|
||||
- Always handle and log errors from `pg` / Drizzle calls
|
||||
|
||||
## Security
|
||||
- Never hardcode secrets in source code
|
||||
- Use environment variables for all credentials
|
||||
- Use environment variables for all credentials (see `.env.example`)
|
||||
- Validate and sanitize all user input
|
||||
- Use parameterized queries (Supabase handles this)
|
||||
- Drizzle uses parameterized queries by default — never build SQL via string concatenation
|
||||
|
||||
## Reference Pattern: Auth Routes
|
||||
`backend/src/routes/auth.ts` is the canonical example new routes should follow:
|
||||
- Zod schema (`credentialsSchema`) parsed with `safeParse`, 400 on failure with `error.issues[0].message`
|
||||
- Passwords hashed with `bcryptjs` (`bcrypt.hash(password, 12)`), never stored or logged in plaintext
|
||||
- Duplicate/invalid-credential checks return generic messages ("Invalid email or password") — never reveal whether the email exists on login
|
||||
- Protected routes use `{ preHandler: app.authenticate }` (decorator defined in `server.ts`, verifies the JWT via `request.jwtVerify()`)
|
||||
- Auth endpoints get a tighter route-level rate limit (`config: { rateLimit: { max: 5, timeWindow: "1 minute" } }`) on top of the global limit
|
||||
- `app.jwt.sign({ sub: user.id, email: user.email })` — the JWT payload shape is declared once in `backend/src/types.d.ts` (`FastifyJWT` module augmentation), so `request.user` is typed everywhere without casts
|
||||
- New protected/authenticated routes should follow this same shape: typed payload via `types.d.ts`, `app.authenticate` as `preHandler`, never `request.user as any`
|
||||
@@ -1,17 +1,16 @@
|
||||
---
|
||||
paths:
|
||||
- "src/components/**"
|
||||
- "src/app/**/page.tsx"
|
||||
- "src/app/**/layout.tsx"
|
||||
- "src/hooks/**"
|
||||
- "frontend/src/components/**"
|
||||
- "frontend/src/**/*.tsx"
|
||||
- "frontend/src/hooks/**"
|
||||
---
|
||||
|
||||
# Frontend Development Rules
|
||||
|
||||
## shadcn/ui First (MANDATORY)
|
||||
- Before creating ANY UI component, check if shadcn/ui has it: `ls src/components/ui/`
|
||||
- Before creating ANY UI component, check if shadcn/ui has it: `ls frontend/src/components/ui/`
|
||||
- NEVER create custom implementations of: Button, Input, Select, Checkbox, Switch, Dialog, Modal, Alert, Toast, Table, Tabs, Card, Badge, Dropdown, Popover, Tooltip, Navigation, Sidebar, Breadcrumb
|
||||
- If a shadcn component is missing, install it: `npx shadcn@latest add <name> --yes`
|
||||
- If a shadcn component is missing, install it: `npx shadcn@latest add <name> --yes` (run inside `frontend/`)
|
||||
- Custom components are ONLY for business-specific compositions that internally use shadcn primitives
|
||||
|
||||
## Import Pattern
|
||||
@@ -28,7 +27,8 @@ import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card"
|
||||
- Keep components small and focused
|
||||
- Use TypeScript interfaces for all props
|
||||
|
||||
## Auth Best Practices (Supabase)
|
||||
- Use `window.location.href` for post-login redirect (not `router.push`)
|
||||
- Always verify `data.session` exists before redirecting
|
||||
## Auth Best Practices (own JWT backend)
|
||||
- Store the JWT in memory/state, not `localStorage`, if XSS exposure is a concern for the project; document the choice in the feature spec
|
||||
- After login, redirect via `react-router`'s `navigate()` once the auth state is confirmed set
|
||||
- Always reset loading state in all code paths (success, error, finally)
|
||||
- Attach the JWT as `Authorization: Bearer <token>` on every API call to `/api/**`
|
||||
@@ -25,8 +25,8 @@ Before starting ANY work, check if the project has been initialized:
|
||||
- Commit format: `type(PROJ-X): description`
|
||||
- Types: feat, fix, refactor, test, docs, deploy, chore
|
||||
- Check existing features before creating new ones: `ls features/ | grep PROJ-`
|
||||
- Check existing components before building: `git ls-files src/components/`
|
||||
- Check existing APIs before building: `git ls-files src/app/api/`
|
||||
- Check existing components before building: `git ls-files frontend/src/components/`
|
||||
- Check existing APIs before building: `git ls-files backend/src/routes/`
|
||||
|
||||
## Human-in-the-Loop
|
||||
- Always ask for user approval before finalizing deliverables
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
---
|
||||
paths:
|
||||
- "src/app/api/**"
|
||||
- "backend/src/**"
|
||||
- ".env*"
|
||||
- "supabase/**"
|
||||
- "next.config.*"
|
||||
- "backend/drizzle.config.ts"
|
||||
---
|
||||
|
||||
# Security Rules
|
||||
|
||||
## Secrets Management
|
||||
- NEVER commit secrets, API keys, or credentials to git
|
||||
- Use `.env.local` for local development (already in .gitignore)
|
||||
- Use `NEXT_PUBLIC_` prefix ONLY for values safe to expose in browser
|
||||
- Document all required env vars in `.env.local.example` with dummy values
|
||||
- Use `.env` for local/server config (already in `.gitignore`), document required vars in `.env.example` with dummy values
|
||||
- Frontend env vars must be explicitly prefixed `VITE_` and contain nothing secret — anything with that prefix ships to the browser
|
||||
|
||||
## Input Validation
|
||||
- Validate ALL user input on the server side with Zod
|
||||
@@ -20,17 +18,18 @@ paths:
|
||||
- Sanitize data before database insertion
|
||||
|
||||
## Authentication
|
||||
- Always verify authentication before processing API requests
|
||||
- Use Supabase RLS as a second line of defense
|
||||
- Implement rate limiting on authentication endpoints
|
||||
- Always verify the JWT (`request.jwtVerify()`) before processing protected API requests
|
||||
- Authorization/ownership checks live in route handlers — there is no database-level RLS backstop, so don't skip them
|
||||
- Implement rate limiting on authentication endpoints (`@fastify/rate-limit`, tighter limits on `/api/auth/**`)
|
||||
|
||||
## Security Headers
|
||||
- X-Frame-Options: DENY
|
||||
- X-Content-Type-Options: nosniff
|
||||
- Referrer-Policy: origin-when-cross-origin
|
||||
- Strict-Transport-Security with includeSubDomains
|
||||
- Set via `@fastify/helmet` on the backend, or nginx `add_header` directives at the reverse proxy
|
||||
|
||||
## Code Review Triggers
|
||||
- Any changes to RLS policies require explicit user approval
|
||||
- Any changes to authentication flow require explicit user approval
|
||||
- Any new environment variables must be documented in .env.local.example
|
||||
- Any changes to database migrations require explicit user approval before running `db:migrate` against a server
|
||||
- Any new environment variables must be documented in `.env.example`
|
||||
@@ -21,8 +21,8 @@ NEVER write code or show implementation details:
|
||||
|
||||
## Before Starting
|
||||
1. Read `features/INDEX.md` to understand project context
|
||||
2. Check existing components: `git ls-files src/components/`
|
||||
3. Check existing APIs: `git ls-files src/app/api/`
|
||||
2. Check existing components: `git ls-files frontend/src/components/`
|
||||
3. Check existing APIs: `git ls-files backend/src/routes/`
|
||||
4. Read the feature spec the user references
|
||||
|
||||
## Workflow
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: backend
|
||||
description: Build APIs, database schemas, and server-side logic with Supabase. Use after frontend is built.
|
||||
description: Build APIs, database schemas, and server-side logic with Fastify + Drizzle + PostgreSQL. Use after frontend is built.
|
||||
argument-hint: [feature-spec-path]
|
||||
user-invocable: true
|
||||
context: fork
|
||||
@@ -11,20 +11,20 @@ model: opus
|
||||
# Backend Developer
|
||||
|
||||
## Role
|
||||
You are an experienced Backend Developer. You read feature specs + tech design and implement APIs, database schemas, and server-side logic using Supabase and Next.js.
|
||||
You are an experienced Backend Developer. You read feature specs + tech design and implement APIs, database schemas, and server-side logic using Fastify, Drizzle ORM, and PostgreSQL.
|
||||
|
||||
## Before Starting
|
||||
1. Read `features/INDEX.md` for project context
|
||||
2. Read the feature spec referenced by the user (including Tech Design section)
|
||||
3. Check existing APIs: `git ls-files src/app/api/`
|
||||
4. Check existing database patterns: `git log --oneline -S "CREATE TABLE" -10`
|
||||
5. Check existing lib files: `ls src/lib/`
|
||||
3. Check existing routes: `git ls-files backend/src/routes/`
|
||||
4. Check existing schema: `cat backend/src/db/schema.ts`
|
||||
5. Check existing migrations: `ls backend/drizzle/`
|
||||
|
||||
## Workflow
|
||||
|
||||
### 1. Read Feature Spec + Design
|
||||
- Understand the data model from Solution Architect
|
||||
- Identify tables, relationships, and RLS requirements
|
||||
- Identify tables, relationships, and authorization requirements
|
||||
- Identify API endpoints needed
|
||||
|
||||
### 2. Ask Technical Questions
|
||||
@@ -35,21 +35,21 @@ Use `AskUserQuestion` for:
|
||||
- What specific input validations are required?
|
||||
|
||||
### 3. Create Database Schema
|
||||
- Write SQL for new tables in Supabase SQL Editor
|
||||
- Enable Row Level Security on EVERY table
|
||||
- Create RLS policies for all CRUD operations
|
||||
- Add/extend tables in `backend/src/db/schema.ts` (Drizzle)
|
||||
- Run `npm run db:generate` to produce the migration, review the generated SQL
|
||||
- Add indexes on performance-critical columns (WHERE, ORDER BY, JOIN)
|
||||
- Use foreign keys with ON DELETE CASCADE where appropriate
|
||||
- Use foreign keys with `onDelete: "cascade"` where appropriate
|
||||
- Run `npm run db:migrate` against the dev database
|
||||
|
||||
### 4. Create API Routes
|
||||
- Create route handlers in `/src/app/api/`
|
||||
- Create route handlers in `backend/src/routes/`, register them in `server.ts`
|
||||
- Implement CRUD operations
|
||||
- Add Zod input validation on all POST/PUT endpoints
|
||||
- Add proper error handling with meaningful messages
|
||||
- Always check authentication (verify user session)
|
||||
- Always check authentication (`request.jwtVerify()`) and authorization (ownership) explicitly — there is no RLS backstop
|
||||
|
||||
### 5. Connect Frontend
|
||||
- Update frontend components to use real API endpoints
|
||||
- Update frontend components to use real API endpoints (`fetch`/`axios` against `/api/**`, proxied by Vite in dev)
|
||||
- Replace any mock data or localStorage with API calls
|
||||
- Handle loading and error states
|
||||
|
||||
@@ -62,30 +62,36 @@ If your context was compacted mid-task:
|
||||
1. Re-read the feature spec you're implementing
|
||||
2. Re-read `features/INDEX.md` for current status
|
||||
3. Run `git diff` to see what you've already changed
|
||||
4. Run `git ls-files src/app/api/` to see current API state
|
||||
4. Run `git ls-files backend/src/routes/` to see current API state
|
||||
5. Continue from where you left off - don't restart or duplicate work
|
||||
|
||||
## Output Format Examples
|
||||
|
||||
### Database Migration
|
||||
```sql
|
||||
CREATE TABLE tasks (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
status TEXT CHECK (status IN ('todo', 'in_progress', 'done')) DEFAULT 'todo',
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
ALTER TABLE tasks ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Users see own tasks" ON tasks
|
||||
FOR SELECT USING (auth.uid() = user_id);
|
||||
|
||||
CREATE INDEX idx_tasks_user_id ON tasks(user_id);
|
||||
CREATE INDEX idx_tasks_status ON tasks(status);
|
||||
### Drizzle Schema
|
||||
```ts
|
||||
export const tasks = pgTable("tasks", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
|
||||
title: text("title").notNull(),
|
||||
status: text("status", { enum: ["todo", "in_progress", "done"] }).notNull().default("todo"),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
}, (table) => ({
|
||||
userIdx: index("idx_tasks_user_id").on(table.userId),
|
||||
statusIdx: index("idx_tasks_status").on(table.status),
|
||||
}));
|
||||
```
|
||||
|
||||
### Route Handler
|
||||
```ts
|
||||
app.get("/api/tasks", { preHandler: app.authenticate }, async (request) => {
|
||||
const userId = request.user.sub;
|
||||
return db.select().from(tasks).where(eq(tasks.userId, userId)).limit(50);
|
||||
});
|
||||
```
|
||||
|
||||
## Reference Implementation
|
||||
`backend/src/routes/auth.ts` + `backend/src/types.d.ts` show the canonical route pattern (Zod validation, `app.authenticate` preHandler, typed JWT payload). Follow this shape for new routes rather than inventing a new one. See [.claude/rules/backend.md](../../rules/backend.md#reference-pattern-auth-routes) for the annotated breakdown.
|
||||
|
||||
## Production References
|
||||
- See [database-optimization.md](../../docs/production/database-optimization.md) for query optimization
|
||||
- See [rate-limiting.md](../../docs/production/rate-limiting.md) for rate limiting setup
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
# Backend Implementation Checklist
|
||||
|
||||
## Core Checklist
|
||||
- [ ] Checked existing tables/APIs via git before creating new ones
|
||||
- [ ] Database tables created in Supabase
|
||||
- [ ] Row Level Security enabled on ALL new tables
|
||||
- [ ] RLS policies created for SELECT, INSERT, UPDATE, DELETE
|
||||
- [ ] Checked existing tables/routes via git before creating new ones
|
||||
- [ ] Database tables added to Drizzle schema (`backend/src/db/schema.ts`)
|
||||
- [ ] Migration generated (`npm run db:generate`) and applied (`npm run db:migrate`)
|
||||
- [ ] Indexes created on performance-critical columns
|
||||
- [ ] Foreign keys set with appropriate ON DELETE behavior
|
||||
- [ ] All planned API endpoints implemented in `/src/app/api/`
|
||||
- [ ] Authentication verified (no access without valid session)
|
||||
- [ ] Foreign keys set with appropriate `onDelete` behavior
|
||||
- [ ] All planned endpoints implemented in `backend/src/routes/` — follow the pattern in `backend/src/routes/auth.ts` (Zod `safeParse`, typed JWT payload via `types.d.ts`, `app.authenticate` preHandler)
|
||||
- [ ] Authentication verified (no access without a valid JWT)
|
||||
- [ ] Ownership/authorization checked explicitly in the route handler
|
||||
- [ ] Input validation with Zod on all POST/PUT requests
|
||||
- [ ] Meaningful error messages with correct HTTP status codes
|
||||
- [ ] No TypeScript errors in API routes
|
||||
- [ ] No TypeScript errors in routes
|
||||
- [ ] All endpoints tested manually
|
||||
- [ ] No hardcoded secrets in source code
|
||||
- [ ] Frontend connected to real API endpoints
|
||||
- [ ] User has reviewed and approved
|
||||
|
||||
## Verification (run before marking complete)
|
||||
- [ ] `npm run build` passes without errors
|
||||
- [ ] All acceptance criteria from feature spec addressed in API
|
||||
- [ ] All API endpoints return correct status codes (test with curl or browser)
|
||||
- [ ] `npm run build` passes without errors (backend)
|
||||
- [ ] All acceptance criteria from feature spec addressed in the API
|
||||
- [ ] All endpoints return correct status codes (test with curl or Thunder Client)
|
||||
- [ ] `features/INDEX.md` status updated to "In Progress"
|
||||
- [ ] Code committed to git
|
||||
|
||||
## Performance Checklist
|
||||
- [ ] All frequently filtered columns have indexes
|
||||
- [ ] No N+1 queries (use Supabase joins instead of loops)
|
||||
- [ ] All list queries use `.limit()`
|
||||
- [ ] No N+1 queries (use Drizzle joins/relational queries instead of loops)
|
||||
- [ ] All list endpoints paginate (limit/offset)
|
||||
- [ ] Zod validation on all write endpoints
|
||||
- [ ] Slow queries cached where appropriate (optional for MVP)
|
||||
- [ ] Rate limiting on public-facing APIs (optional for MVP)
|
||||
- [ ] Rate limiting on public-facing routes (optional for MVP)
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: deploy
|
||||
description: Deploy to Vercel with production-ready checks, error tracking, and security headers setup.
|
||||
argument-hint: [feature-spec-path or "to Vercel"]
|
||||
description: Deploy to a self-managed Linux server via git pull + rsync + systemd, with production-ready checks.
|
||||
argument-hint: [feature-spec-path or "to production"]
|
||||
user-invocable: true
|
||||
allowed-tools: Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion
|
||||
model: sonnet
|
||||
@@ -10,7 +10,16 @@ model: sonnet
|
||||
# DevOps Engineer
|
||||
|
||||
## Role
|
||||
You are an experienced DevOps Engineer handling deployment, environment setup, and production readiness.
|
||||
You are an experienced DevOps Engineer handling deployment, environment setup, and production readiness on a self-hosted server (no PaaS/Vercel).
|
||||
|
||||
## Server Layout Convention
|
||||
Checkout and running service are kept separate, so a `git pull` never silently swaps out a running process's code:
|
||||
|
||||
```
|
||||
/opt/<project>/repo git checkout only, pulled from Gitea
|
||||
/opt/<project>/backend running backend service, own .env, own node_modules
|
||||
/var/www/<project> built frontend static files, served by nginx
|
||||
```
|
||||
|
||||
## Before Starting
|
||||
1. Read `features/INDEX.md` to know what is being deployed
|
||||
@@ -21,83 +30,90 @@ You are an experienced DevOps Engineer handling deployment, environment setup, a
|
||||
## Workflow
|
||||
|
||||
### 1. Pre-Deployment Checks
|
||||
- [ ] `npm run build` succeeds locally
|
||||
- [ ] `npm run build` succeeds locally (frontend and backend)
|
||||
- [ ] `npm run lint` passes
|
||||
- [ ] QA Engineer has approved the feature (check feature spec)
|
||||
- [ ] No Critical/High bugs in test report
|
||||
- [ ] All environment variables documented in `.env.local.example`
|
||||
- [ ] All environment variables documented in `.env.example`
|
||||
- [ ] No secrets committed to git
|
||||
- [ ] All database migrations applied in Supabase (if applicable)
|
||||
- [ ] All code committed and pushed to remote
|
||||
- [ ] All Drizzle migrations generated and committed
|
||||
- [ ] All code committed and pushed to Gitea
|
||||
|
||||
### 2. Vercel Setup (first deployment only)
|
||||
Guide the user through:
|
||||
- [ ] Create Vercel project: `npx vercel` or via vercel.com
|
||||
- [ ] Connect GitHub repository for auto-deploy on push
|
||||
- [ ] Add all environment variables from `.env.local.example` in Vercel Dashboard
|
||||
- [ ] Build settings: Framework Preset = Next.js (auto-detected)
|
||||
- [ ] Configure domain (or use default `*.vercel.app`)
|
||||
### 2. Server Setup (first deployment only)
|
||||
Guide the user through, on the target server:
|
||||
- [ ] `git clone <gitea-url> /opt/<project>/repo`
|
||||
- [ ] Create `/opt/<project>/backend`, copy backend files in, create `.env` from `.env.example`
|
||||
- [ ] Create PostgreSQL database + role for the app
|
||||
- [ ] Create a systemd unit for the backend (`/etc/systemd/system/<project>-backend.service`), `ExecStart=node dist/server.js`, `WorkingDirectory=/opt/<project>/backend`, `EnvironmentFile=/opt/<project>/backend/.env`
|
||||
- [ ] Configure nginx: reverse proxy `/api/` to the backend port, serve `/var/www/<project>` as static root for everything else
|
||||
- [ ] `systemctl enable --now <project>-backend`
|
||||
|
||||
### 3. Deploy
|
||||
- Push to main branch → Vercel auto-deploys
|
||||
- Or manual: `npx vercel --prod`
|
||||
- Monitor build in Vercel Dashboard
|
||||
### 3. Deploy (update)
|
||||
Run from a machine with SSH access to the server:
|
||||
```bash
|
||||
ssh <user>@<server> "cd /opt/<project>/repo && git pull --ff-only \
|
||||
&& rsync -a --exclude='.env' --exclude='node_modules' /opt/<project>/repo/backend/ /opt/<project>/backend/ \
|
||||
&& cd /opt/<project>/backend && npm ci --omit=dev && npm run db:migrate \
|
||||
&& systemctl restart <project>-backend \
|
||||
&& cd /opt/<project>/repo/frontend && npm ci && npm run build \
|
||||
&& rsync -a --delete dist/ /var/www/<project>/"
|
||||
```
|
||||
- Frontend-only change → skip the backend rsync/migrate/restart block, just rebuild and rsync `dist/`
|
||||
- Updates are pulled from Gitea (`git pull --ff-only`) — never edit code directly on the server
|
||||
|
||||
### 4. Post-Deployment Verification
|
||||
- [ ] Production URL loads correctly
|
||||
- [ ] Deployed feature works as expected
|
||||
- [ ] Database connections work (if applicable)
|
||||
- [ ] Database connections work (check backend logs: `journalctl -u <project>-backend -f`)
|
||||
- [ ] Authentication flows work (if applicable)
|
||||
- [ ] No errors in browser console
|
||||
- [ ] No errors in Vercel function logs
|
||||
- [ ] No errors in `journalctl -u <project>-backend`
|
||||
|
||||
### 5. Production-Ready Essentials
|
||||
|
||||
For first deployment, guide the user through these setup guides:
|
||||
|
||||
**Error Tracking (5 min):** See [error-tracking.md](../../docs/production/error-tracking.md)
|
||||
**Security Headers (copy-paste):** See [security-headers.md](../../docs/production/security-headers.md)
|
||||
**Error Tracking:** See [error-tracking.md](../../docs/production/error-tracking.md)
|
||||
**Security Headers:** See [security-headers.md](../../docs/production/security-headers.md)
|
||||
**Performance Check:** See [performance.md](../../docs/production/performance.md)
|
||||
**Database Optimization:** See [database-optimization.md](../../docs/production/database-optimization.md)
|
||||
**Rate Limiting (optional):** See [rate-limiting.md](../../docs/production/rate-limiting.md)
|
||||
**Rate Limiting:** See [rate-limiting.md](../../docs/production/rate-limiting.md)
|
||||
|
||||
### 6. Post-Deployment Bookkeeping
|
||||
- Update feature spec: Add deployment section with production URL and date
|
||||
- Update feature spec: Add deployment section with date
|
||||
- Update `features/INDEX.md`: Set status to **Deployed**
|
||||
- Create git tag: `git tag -a v1.X.0-PROJ-X -m "Deploy PROJ-X: [Feature Name]"`
|
||||
- Push tag: `git push origin v1.X.0-PROJ-X`
|
||||
|
||||
## Common Issues
|
||||
|
||||
### Build fails on Vercel but works locally
|
||||
- Check Node.js version (Vercel may use different version)
|
||||
- Ensure all dependencies are in package.json (not just devDependencies)
|
||||
- Review Vercel build logs for specific error
|
||||
### Build works locally but fails on server
|
||||
- Check Node.js version matches between local and server (`node -v`)
|
||||
- Ensure all dependencies are in `dependencies`, not just `devDependencies`, if `npm ci --omit=dev` is used for the backend
|
||||
|
||||
### Environment variables not available
|
||||
- Verify vars are set in Vercel Dashboard (Settings → Environment Variables)
|
||||
- Client-side vars need `NEXT_PUBLIC_` prefix
|
||||
- Redeploy after adding new env vars (they don't apply retroactively)
|
||||
- Verify `.env` exists in `/opt/<project>/backend` (it is excluded from rsync on purpose, so it survives deploys)
|
||||
- Frontend vars need the `VITE_` prefix and are baked in at build time — rebuild after changing them
|
||||
|
||||
### Database connection errors
|
||||
- Verify Supabase URL and anon key in Vercel env vars
|
||||
- Check RLS policies allow the operations being attempted
|
||||
- Verify Supabase project is not paused (free tier pauses after inactivity)
|
||||
- Verify `DATABASE_URL` in the server's `.env`
|
||||
- Check the app's database role has the needed grants
|
||||
- `journalctl -u <project>-backend` for the actual driver error
|
||||
|
||||
## Rollback Instructions
|
||||
If production is broken:
|
||||
1. **Immediate:** Vercel Dashboard → Deployments → Click "..." on previous working deployment → "Promote to Production"
|
||||
2. **Fix locally:** Debug the issue, `npm run build`, commit, push
|
||||
3. Vercel auto-deploys the fix
|
||||
1. **Immediate:** on the server, `cd /opt/<project>/repo && git checkout <last-good-tag>`, redo step 3's rsync/build for that commit
|
||||
2. **Fix locally:** debug the issue, `npm run build`, commit, push
|
||||
3. Redeploy via step 3
|
||||
|
||||
## Full Deployment Checklist
|
||||
- [ ] Pre-deployment checks all pass
|
||||
- [ ] Vercel build successful
|
||||
- [ ] Build successful (frontend + backend)
|
||||
- [ ] Production URL loads and works
|
||||
- [ ] Feature tested in production environment
|
||||
- [ ] No console errors, no Vercel log errors
|
||||
- [ ] Error tracking setup (Sentry or alternative)
|
||||
- [ ] Security headers configured in next.config
|
||||
- [ ] No console errors, no backend log errors
|
||||
- [ ] Error tracking setup
|
||||
- [ ] Security headers configured (nginx or `@fastify/helmet`)
|
||||
- [ ] Lighthouse score checked (target > 90)
|
||||
- [ ] Feature spec updated with deployment info
|
||||
- [ ] `features/INDEX.md` updated to Deployed
|
||||
@@ -108,6 +124,5 @@ If production is broken:
|
||||
```
|
||||
deploy(PROJ-X): Deploy [feature name] to production
|
||||
|
||||
- Production URL: https://your-app.vercel.app
|
||||
- Deployed: YYYY-MM-DD
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: frontend
|
||||
description: Build UI components with React, Next.js, Tailwind CSS, and shadcn/ui. Use after architecture is designed.
|
||||
description: Build UI components with React, Vite, Tailwind CSS, and shadcn/ui. Use after architecture is designed.
|
||||
argument-hint: [feature-spec-path]
|
||||
user-invocable: true
|
||||
context: fork
|
||||
@@ -11,15 +11,15 @@ model: opus
|
||||
# Frontend Developer
|
||||
|
||||
## Role
|
||||
You are an experienced Frontend Developer. You read feature specs + tech design and implement the UI using React, Next.js, Tailwind CSS, and shadcn/ui.
|
||||
You are an experienced Frontend Developer. You read feature specs + tech design and implement the UI using React, Vite, Tailwind CSS, and shadcn/ui.
|
||||
|
||||
## Before Starting
|
||||
1. Read `features/INDEX.md` for project context
|
||||
2. Read the feature spec referenced by the user (including Tech Design section)
|
||||
3. Check installed shadcn/ui components: `ls src/components/ui/`
|
||||
4. Check existing custom components: `ls src/components/*.tsx 2>/dev/null`
|
||||
5. Check existing hooks: `ls src/hooks/ 2>/dev/null`
|
||||
6. Check existing pages: `ls src/app/`
|
||||
3. Check installed shadcn/ui components: `ls frontend/src/components/ui/`
|
||||
4. Check existing custom components: `ls frontend/src/components/*.tsx 2>/dev/null`
|
||||
5. Check existing hooks: `ls frontend/src/hooks/ 2>/dev/null`
|
||||
6. Check existing pages/routes: `ls frontend/src/`
|
||||
|
||||
## Workflow
|
||||
|
||||
@@ -43,19 +43,19 @@ If no design specs exist, ask the user:
|
||||
- Accessibility requirements beyond defaults (WCAG 2.1 AA)?
|
||||
|
||||
### 4. Implement Components
|
||||
- Create components in `/src/components/`
|
||||
- ALWAYS use shadcn/ui for standard UI elements (check `src/components/ui/` first!)
|
||||
- If a shadcn component is missing, install it: `npx shadcn@latest add <name> --yes`
|
||||
- Create components in `frontend/src/components/`
|
||||
- ALWAYS use shadcn/ui for standard UI elements (check `frontend/src/components/ui/` first!)
|
||||
- If a shadcn component is missing, install it: `npx shadcn@latest add <name> --yes` (run inside `frontend/`)
|
||||
- Only create custom components as compositions of shadcn primitives
|
||||
- Use Tailwind CSS for all styling
|
||||
|
||||
### 5. Integrate into Pages
|
||||
- Add components to pages in `/src/app/`
|
||||
- Set up routing if needed
|
||||
- Connect to backend APIs or localStorage as specified in tech design
|
||||
- Add components to routes/pages in `frontend/src/`
|
||||
- Set up routing (`react-router-dom`) if needed
|
||||
- Connect to backend APIs (`/api/**`, proxied by Vite in dev) or localStorage as specified in tech design
|
||||
|
||||
### 6. User Review
|
||||
- Tell the user to test in browser (localhost:3000)
|
||||
- Tell the user to test in browser (localhost:5173)
|
||||
- Ask: "Does the UI look right? Any changes needed?"
|
||||
- Iterate based on feedback
|
||||
|
||||
@@ -64,7 +64,7 @@ If your context was compacted mid-task:
|
||||
1. Re-read the feature spec you're implementing
|
||||
2. Re-read `features/INDEX.md` for current status
|
||||
3. Run `git diff` to see what you've already changed
|
||||
4. Run `git ls-files src/components/ | head -20` to see current component state
|
||||
4. Run `git ls-files frontend/src/components/ | head -20` to see current component state
|
||||
5. Continue from where you left off - don't restart or duplicate work
|
||||
|
||||
## After Completion: Backend & QA Handoff
|
||||
|
||||
@@ -31,9 +31,9 @@ Read these files to understand where the project stands:
|
||||
- Deployment section exists (added by /deploy)
|
||||
|
||||
4. **Check Codebase:** Quick scan of what's been built
|
||||
- `ls src/components/*.tsx 2>/dev/null` → Custom components
|
||||
- `ls src/app/api/ 2>/dev/null` → API routes
|
||||
- `ls src/components/ui/` → Installed shadcn components
|
||||
- `ls frontend/src/components/*.tsx 2>/dev/null` → Custom components
|
||||
- `ls backend/src/routes/ 2>/dev/null` → API routes
|
||||
- `ls frontend/src/components/ui/` → Installed shadcn components
|
||||
|
||||
### Step 2: Determine Next Action
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ Add this section to the END of the feature spec `/features/PROJ-X.md`:
|
||||
## QA Test Results
|
||||
|
||||
**Tested:** YYYY-MM-DD
|
||||
**App URL:** http://localhost:3000
|
||||
**App URL:** http://localhost:5173
|
||||
**Tester:** QA Engineer (AI)
|
||||
|
||||
### Acceptance Criteria Status
|
||||
|
||||
@@ -100,8 +100,8 @@ feat: Initialize project - PRD and X feature specifications
|
||||
Use this mode when the project already has a PRD and the user wants to add a new feature.
|
||||
|
||||
### Phase 1: Understand the Feature
|
||||
1. Check existing components: `git ls-files src/components/`
|
||||
2. Check existing APIs: `git ls-files src/app/api/`
|
||||
1. Check existing components: `git ls-files frontend/src/components/`
|
||||
2. Check existing APIs: `git ls-files backend/src/routes/`
|
||||
3. Ensure you are not duplicating an existing feature
|
||||
|
||||
Ask the user interactive questions to clarify:
|
||||
|
||||
@@ -11,12 +11,15 @@
|
||||
- As a [user type], I want to [action] so that [goal]
|
||||
|
||||
## Acceptance Criteria
|
||||
_Each one must be testable by a QA engineer without asking a follow-up question._
|
||||
- [ ] Criterion 1
|
||||
- [ ] Criterion 2
|
||||
|
||||
## Edge Cases
|
||||
- What happens when...?
|
||||
- How do we handle...?
|
||||
_Concrete failure/boundary scenarios, not rhetorical questions — state the expected behavior._
|
||||
- Duplicate input: [expected behavior]
|
||||
- Network/API failure mid-action: [expected behavior]
|
||||
- Concurrent edits by two users: [expected behavior]
|
||||
|
||||
## Technical Requirements (optional)
|
||||
- Performance: < 200ms response time
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Supabase (Optional - remove if not using backend)
|
||||
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url_here
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key_here
|
||||
|
||||
# Add more environment variables as needed
|
||||
# STRIPE_SECRET_KEY=sk_...
|
||||
# SMTP_HOST=smtp.sendgrid.net
|
||||
# SMTP_USER=your_smtp_user
|
||||
# SMTP_PASS=your_smtp_password
|
||||
@@ -1,20 +1,14 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
node_modules/
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
# build output
|
||||
frontend/dist/
|
||||
backend/dist/
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
# env files
|
||||
.env
|
||||
.env.local
|
||||
!*.env.example
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
@@ -25,15 +19,8 @@ npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# claude code personal settings
|
||||
.claude/settings.local.json
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
@@ -1,30 +1,37 @@
|
||||
# AI Coding Starter Kit
|
||||
|
||||
> A Next.js template with an AI-powered development workflow using specialized skills for Requirements, Architecture, Frontend, Backend, QA, and Deployment.
|
||||
> A self-hosted Vite/React + Fastify template with an AI-powered development workflow using specialized skills for Requirements, Architecture, Frontend, Backend, QA, and Deployment.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Framework:** Next.js 16 (App Router), TypeScript
|
||||
- **Frontend:** Vite + React 19 + TypeScript
|
||||
- **Styling:** Tailwind CSS + shadcn/ui (copy-paste components)
|
||||
- **Backend:** Supabase (PostgreSQL + Auth + Storage) - optional
|
||||
- **Deployment:** Vercel
|
||||
- **Backend:** Fastify + Drizzle ORM + PostgreSQL, own JWT auth
|
||||
- **Deployment:** self-hosted Linux server — git pull + rsync + systemd, updates pulled from Gitea
|
||||
- **Validation:** Zod + react-hook-form
|
||||
- **State:** React useState / Context API
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
src/
|
||||
app/ Pages (Next.js App Router)
|
||||
frontend/
|
||||
src/
|
||||
main.tsx, App.tsx Entry points
|
||||
components/
|
||||
ui/ shadcn/ui components (NEVER recreate these)
|
||||
hooks/ Custom React hooks
|
||||
lib/ Utilities (supabase.ts, utils.ts)
|
||||
lib/ Utilities (utils.ts)
|
||||
backend/
|
||||
src/
|
||||
server.ts Fastify app entry
|
||||
routes/ Route handlers
|
||||
db/ Drizzle schema + connection
|
||||
drizzle/ Generated migrations
|
||||
features/ Feature specifications (PROJ-X-name.md)
|
||||
INDEX.md Feature status overview
|
||||
docs/
|
||||
PRD.md Product Requirements Document
|
||||
production/ Production guides (Sentry, security, performance)
|
||||
production/ Production guides (error tracking, security, performance)
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
@@ -32,9 +39,9 @@ docs/
|
||||
1. `/requirements` - Create feature spec from idea
|
||||
2. `/architecture` - Design tech architecture (PM-friendly, no code)
|
||||
3. `/frontend` - Build UI components (shadcn/ui first!)
|
||||
4. `/backend` - Build APIs, database, RLS policies
|
||||
4. `/backend` - Build APIs, database schema, auth checks
|
||||
5. `/qa` - Test against acceptance criteria + security audit
|
||||
6. `/deploy` - Deploy to Vercel + production-ready checks
|
||||
6. `/deploy` - Deploy to the server + production-ready checks
|
||||
|
||||
## Feature Tracking
|
||||
|
||||
@@ -47,14 +54,22 @@ All features tracked in `features/INDEX.md`. Every skill reads it at start and u
|
||||
- **Single Responsibility:** One feature per spec file
|
||||
- **shadcn/ui first:** NEVER create custom versions of installed shadcn components
|
||||
- **Human-in-the-loop:** All workflows have user approval checkpoints
|
||||
- **No RLS backstop:** authorization/ownership checks are explicit in Fastify route handlers, not enforced by the database
|
||||
|
||||
## Build & Test Commands
|
||||
|
||||
```bash
|
||||
npm run dev # Development server (localhost:3000)
|
||||
# frontend/
|
||||
npm run dev # Vite dev server (localhost:5173)
|
||||
npm run build # Production build
|
||||
npm run lint # ESLint
|
||||
npm run start # Production server
|
||||
|
||||
# backend/
|
||||
npm run dev # tsx watch (localhost:4000)
|
||||
npm run build # tsc build
|
||||
npm run start # node dist/server.js
|
||||
npm run db:generate # generate Drizzle migration
|
||||
npm run db:migrate # apply migrations
|
||||
```
|
||||
|
||||
## Product Context
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# AI Coding Starter Kit
|
||||
|
||||
> Build production-ready web apps faster with AI-powered Skills handling Requirements, Architecture, Development, QA, and Deployment.
|
||||
> Build production-ready web apps faster with AI-powered Skills handling Requirements, Architecture, Development, QA, and Deployment. Self-hosted, no vendor lock-in — Vite/React + Fastify/PostgreSQL, deployed via git pull + rsync + systemd.
|
||||
|
||||
This template uses [Claude Code](https://docs.anthropic.com/en/docs/claude-code) with modern Skills, Rules, and Sub-Agents to provide a complete AI-powered development workflow.
|
||||
|
||||
@@ -9,30 +9,28 @@ This template uses [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
|
||||
### 1. Clone & Install
|
||||
|
||||
```bash
|
||||
git clone https://github.com/YOUR_USERNAME/ai-coding-starter-kit.git my-project
|
||||
git clone <your-gitea-url>/ai-coding-starter-kit.git my-project
|
||||
cd my-project
|
||||
npm install
|
||||
cd frontend && npm install && cd ..
|
||||
cd backend && npm install && cd ..
|
||||
```
|
||||
|
||||
### 2. (Optional) Supabase Setup
|
||||
### 2. Set Up PostgreSQL
|
||||
|
||||
If you need a backend:
|
||||
1. Create a local PostgreSQL database and role for the app
|
||||
2. Copy `backend/.env.example` to `backend/.env`
|
||||
3. Set `DATABASE_URL` and `JWT_SECRET`
|
||||
4. Run `cd backend && npm run db:generate && npm run db:migrate`
|
||||
|
||||
1. Create Supabase Project: [supabase.com](https://supabase.com)
|
||||
2. Copy `.env.local.example` to `.env.local`
|
||||
3. Add your Supabase credentials
|
||||
4. Uncomment the Supabase client in `src/lib/supabase.ts`
|
||||
|
||||
Skip this step if you're building frontend-only (landing pages, portfolios, etc.)
|
||||
Skip the backend entirely if you're building frontend-only (landing pages, portfolios, etc.) — just run the frontend.
|
||||
|
||||
### 3. Start Development
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
cd backend && npm run dev # localhost:4000
|
||||
cd frontend && npm run dev # localhost:5173, proxies /api to the backend
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) in your browser.
|
||||
|
||||
### 4. Initialize Your Project
|
||||
|
||||
Open Claude Code and describe your project. The `/requirements` skill automatically detects that this is a fresh project and enters **Init Mode**:
|
||||
@@ -61,7 +59,7 @@ After project initialization, build features one at a time using skills:
|
||||
/frontend Build the UI for features/PROJ-1-user-auth.md
|
||||
/backend Build the API for features/PROJ-1-user-auth.md
|
||||
/qa Test features/PROJ-1-user-auth.md
|
||||
/deploy Deploy to Vercel
|
||||
/deploy Deploy to your server
|
||||
```
|
||||
|
||||
Each skill suggests the next step when it finishes. Handoffs are always user-initiated.
|
||||
@@ -77,9 +75,9 @@ To add more features later, run `/requirements` again - it detects the existing
|
||||
| Requirements Engineer | `/requirements` | Creates feature specs with user stories, acceptance criteria, edge cases |
|
||||
| Solution Architect | `/architecture` | Designs PM-friendly tech architecture (no code, only high-level design) |
|
||||
| Frontend Developer | `/frontend` | Builds UI with React, Tailwind CSS, and shadcn/ui |
|
||||
| Backend Developer | `/backend` | Builds APIs, database schemas, RLS policies with Supabase |
|
||||
| Backend Developer | `/backend` | Builds APIs, database schema, and auth with Fastify + Drizzle + PostgreSQL |
|
||||
| QA Engineer | `/qa` | Tests features against acceptance criteria + security audit |
|
||||
| DevOps | `/deploy` | Deploys to Vercel with production-ready checks |
|
||||
| DevOps | `/deploy` | Deploys to your server (git pull + rsync + systemd) with production-ready checks |
|
||||
| Help | `/help` | Context-aware guide: shows where you are and what to do next |
|
||||
|
||||
### How Skills Work
|
||||
@@ -99,7 +97,7 @@ To add more features later, run `/requirements` again - it detects the existing
|
||||
3. Build /frontend --> UI components implemented
|
||||
/backend --> APIs + database (if needed)
|
||||
4. Test /qa --> Test results added to feature spec
|
||||
5. Ship /deploy --> Deployed to Vercel
|
||||
5. Ship /deploy --> Deployed to your server
|
||||
```
|
||||
|
||||
### Feature Tracking
|
||||
@@ -119,12 +117,13 @@ Every skill reads this file at start and updates it when done, preventing duplic
|
||||
|
||||
| Category | Tool | Why? |
|
||||
|----------|------|------|
|
||||
| **Framework** | Next.js 16 | React + Server Components + App Router |
|
||||
| **Frontend** | Vite + React 19 | Fast dev server, no framework lock-in |
|
||||
| **Language** | TypeScript | Type safety |
|
||||
| **Styling** | Tailwind CSS | Utility-first CSS |
|
||||
| **UI Library** | shadcn/ui | Copy-paste, customizable components |
|
||||
| **Backend** | Supabase (optional) | PostgreSQL + Auth + Storage + Realtime |
|
||||
| **Deployment** | Vercel | Zero-config Next.js hosting |
|
||||
| **Backend** | Fastify + Drizzle + PostgreSQL | Lightweight, typed, self-hostable |
|
||||
| **Auth** | Own JWT (`@fastify/jwt`) | No third-party auth dependency |
|
||||
| **Deployment** | git pull + rsync + systemd | Runs on any Linux server, updates pulled from Gitea |
|
||||
| **Validation** | Zod | Runtime type validation |
|
||||
|
||||
---
|
||||
@@ -140,7 +139,7 @@ ai-coding-starter-kit/
|
||||
| +-- rules/ <-- Auto-applied coding rules
|
||||
| | +-- general.md Git workflow, feature tracking
|
||||
| | +-- frontend.md shadcn/ui, component standards
|
||||
| | +-- backend.md RLS, validation, queries
|
||||
| | +-- backend.md Validation, queries, migrations
|
||||
| | +-- security.md Secrets, headers, auth
|
||||
| +-- skills/ <-- Invocable workflows (/command)
|
||||
| | +-- requirements/SKILL.md /requirements
|
||||
@@ -160,18 +159,23 @@ ai-coding-starter-kit/
|
||||
+-- docs/
|
||||
| +-- PRD.md <-- Product Requirements Document
|
||||
| +-- production/ <-- Production setup guides
|
||||
| +-- error-tracking.md Sentry setup (5 min)
|
||||
| +-- error-tracking.md Error tracking setup
|
||||
| +-- security-headers.md XSS/Clickjacking protection
|
||||
| +-- performance.md Lighthouse, optimization
|
||||
| +-- database-optimization.md Indexing, N+1, caching
|
||||
| +-- rate-limiting.md Upstash Redis
|
||||
+-- src/
|
||||
| +-- app/ <-- Pages (Next.js App Router)
|
||||
| +-- components/
|
||||
| | +-- ui/ <-- shadcn/ui components (35+ installed)
|
||||
| +-- hooks/ <-- Custom React hooks
|
||||
| +-- lib/ <-- Utilities
|
||||
+-- public/ <-- Static files
|
||||
| +-- rate-limiting.md @fastify/rate-limit
|
||||
+-- frontend/
|
||||
| +-- src/
|
||||
| | +-- main.tsx, App.tsx <-- Entry points
|
||||
| | +-- components/ui/ <-- shadcn/ui components (35+ installed)
|
||||
| | +-- hooks/ <-- Custom React hooks
|
||||
| | +-- lib/ <-- Utilities
|
||||
| +-- public/ <-- Static files
|
||||
+-- backend/
|
||||
+-- src/
|
||||
+-- server.ts <-- Fastify entry
|
||||
+-- routes/ <-- Route handlers
|
||||
+-- db/ <-- Drizzle schema + connection
|
||||
```
|
||||
|
||||
---
|
||||
@@ -195,7 +199,7 @@ Run `/requirements` with your feature idea. The skill will:
|
||||
|
||||
### 3. Add shadcn/ui Components (as needed)
|
||||
|
||||
35+ components are pre-installed. Add more as needed:
|
||||
35+ components are pre-installed. Add more as needed (run inside `frontend/`):
|
||||
```bash
|
||||
npx shadcn@latest add [component-name]
|
||||
```
|
||||
@@ -203,8 +207,8 @@ npx shadcn@latest add [component-name]
|
||||
### 4. Production Setup (first deployment)
|
||||
|
||||
When you're ready to deploy, the `/deploy` skill guides you through:
|
||||
- Vercel setup and deployment
|
||||
- Error tracking with Sentry
|
||||
- Server setup (systemd unit, nginx reverse proxy, PostgreSQL)
|
||||
- Error tracking
|
||||
- Security headers configuration
|
||||
- Performance monitoring with Lighthouse
|
||||
|
||||
@@ -222,7 +226,7 @@ Each skill is a structured workflow that Claude Code discovers automatically. Sk
|
||||
| `/requirements` | Inline | Needs live interaction with user |
|
||||
| `/architecture` | Inline | Short output, user reviews in real-time |
|
||||
| `/frontend` | Sub-agent (forked) | Heavy file editing, lots of output |
|
||||
| `/backend` | Sub-agent (forked) | Heavy file editing, SQL, API code |
|
||||
| `/backend` | Sub-agent (forked) | Heavy file editing, schema, route code |
|
||||
| `/qa` | Sub-agent (forked) | Systematic testing, lots of output |
|
||||
| `/deploy` | Inline | Deployment needs user oversight |
|
||||
| `/help` | Inline | Quick status check and guidance |
|
||||
@@ -288,36 +292,32 @@ This template is designed as a starting point. Customize it for your team:
|
||||
|
||||
Standalone guides in `docs/production/`:
|
||||
|
||||
| Guide | Setup Time | What It Does |
|
||||
|-------|-----------|-------------|
|
||||
| [Error Tracking](docs/production/error-tracking.md) | 5 min | Sentry integration for automatic error capture |
|
||||
| [Security Headers](docs/production/security-headers.md) | 2 min | XSS, Clickjacking, MIME sniffing protection |
|
||||
| [Performance](docs/production/performance.md) | 10 min | Lighthouse checks, image optimization, caching |
|
||||
| [Database Optimization](docs/production/database-optimization.md) | 15 min | Indexing, N+1 prevention, query optimization |
|
||||
| [Rate Limiting](docs/production/rate-limiting.md) | 10 min | Upstash Redis for API abuse prevention |
|
||||
| Guide | What It Does |
|
||||
|-------|-------------|
|
||||
| [Error Tracking](docs/production/error-tracking.md) | Automatic error capture setup |
|
||||
| [Security Headers](docs/production/security-headers.md) | XSS, Clickjacking, MIME sniffing protection |
|
||||
| [Performance](docs/production/performance.md) | Lighthouse checks, image optimization, caching |
|
||||
| [Database Optimization](docs/production/database-optimization.md) | Indexing, N+1 prevention, query optimization |
|
||||
| [Rate Limiting](docs/production/rate-limiting.md) | `@fastify/rate-limit` for API abuse prevention |
|
||||
|
||||
---
|
||||
|
||||
## Scripts
|
||||
|
||||
```bash
|
||||
npm run dev # Development server (localhost:3000)
|
||||
# frontend/
|
||||
npm run dev # Vite dev server (localhost:5173)
|
||||
npm run build # Production build
|
||||
npm run start # Production server
|
||||
npm run lint # ESLint
|
||||
|
||||
# backend/
|
||||
npm run dev # tsx watch (localhost:4000)
|
||||
npm run build # tsc build
|
||||
npm run start # node dist/server.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Author
|
||||
|
||||
Created by **Alex Sprogis** – AI Product Engineer & Content Creator.
|
||||
|
||||
- [YouTube](https://www.youtube.com/@alex.sprogis)
|
||||
- [Website](https://alexsprogis.de)
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT License - feel free to use for your projects!
|
||||
@@ -0,0 +1,3 @@
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/app
|
||||
JWT_SECRET=change-me
|
||||
PORT=4000
|
||||
@@ -0,0 +1,10 @@
|
||||
import { defineConfig } from "drizzle-kit";
|
||||
|
||||
export default defineConfig({
|
||||
schema: "./src/db/schema.ts",
|
||||
out: "./drizzle",
|
||||
dialect: "postgresql",
|
||||
dbCredentials: {
|
||||
url: process.env.DATABASE_URL!,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "ai-coding-starter-kit-backend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "tsx watch src/server.ts",
|
||||
"build": "tsc -p .",
|
||||
"start": "node dist/server.js",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/cors": "^10.0.1",
|
||||
"@fastify/jwt": "^10.2.2",
|
||||
"@fastify/rate-limit": "^10.1.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"dotenv": "^16.4.5",
|
||||
"drizzle-orm": "^0.45.2",
|
||||
"fastify": "^5.1.0",
|
||||
"pg": "^8.13.1",
|
||||
"zod": "^4.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/node": "^20",
|
||||
"@types/pg": "^8.11.10",
|
||||
"drizzle-kit": "^0.31.10",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import { Pool } from "pg";
|
||||
import * as schema from "./schema.js";
|
||||
|
||||
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
|
||||
|
||||
export const db = drizzle(pool, { schema });
|
||||
@@ -0,0 +1,8 @@
|
||||
import { pgTable, uuid, text, timestamp } from "drizzle-orm/pg-core";
|
||||
|
||||
export const users = pgTable("users", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
email: text("email").notNull().unique(),
|
||||
passwordHash: text("password_hash").notNull(),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import bcrypt from "bcryptjs";
|
||||
import { z } from "zod";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../db/index.js";
|
||||
import { users } from "../db/schema.js";
|
||||
|
||||
const credentialsSchema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(8),
|
||||
});
|
||||
|
||||
export async function authRoutes(app: FastifyInstance) {
|
||||
app.post("/api/auth/register", {
|
||||
config: { rateLimit: { max: 5, timeWindow: "1 minute" } },
|
||||
}, async (request, reply) => {
|
||||
const parsed = credentialsSchema.safeParse(request.body);
|
||||
if (!parsed.success) {
|
||||
return reply.code(400).send({ error: parsed.error.issues[0].message });
|
||||
}
|
||||
const { email, password } = parsed.data;
|
||||
|
||||
const existing = await db.select().from(users).where(eq(users.email, email)).limit(1);
|
||||
if (existing.length > 0) {
|
||||
return reply.code(409).send({ error: "Email already registered" });
|
||||
}
|
||||
|
||||
const passwordHash = await bcrypt.hash(password, 12);
|
||||
const [user] = await db
|
||||
.insert(users)
|
||||
.values({ email, passwordHash })
|
||||
.returning({ id: users.id, email: users.email });
|
||||
|
||||
const token = app.jwt.sign({ sub: user.id, email: user.email });
|
||||
return reply.code(201).send({ token });
|
||||
});
|
||||
|
||||
app.post("/api/auth/login", {
|
||||
config: { rateLimit: { max: 5, timeWindow: "1 minute" } },
|
||||
}, async (request, reply) => {
|
||||
const parsed = credentialsSchema.safeParse(request.body);
|
||||
if (!parsed.success) {
|
||||
return reply.code(400).send({ error: parsed.error.issues[0].message });
|
||||
}
|
||||
const { email, password } = parsed.data;
|
||||
|
||||
const [user] = await db.select().from(users).where(eq(users.email, email)).limit(1);
|
||||
if (!user || !(await bcrypt.compare(password, user.passwordHash))) {
|
||||
return reply.code(401).send({ error: "Invalid email or password" });
|
||||
}
|
||||
|
||||
const token = app.jwt.sign({ sub: user.id, email: user.email });
|
||||
return reply.send({ token });
|
||||
});
|
||||
|
||||
app.get("/api/auth/me", { preHandler: app.authenticate }, async (request) => {
|
||||
const { sub, email } = request.user;
|
||||
return { id: sub, email };
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { FastifyInstance } from "fastify";
|
||||
|
||||
export async function healthRoutes(app: FastifyInstance) {
|
||||
app.get("/api/health", async () => ({ status: "ok" }));
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import "dotenv/config";
|
||||
import Fastify, { type FastifyRequest, type FastifyReply } from "fastify";
|
||||
import cors from "@fastify/cors";
|
||||
import jwt from "@fastify/jwt";
|
||||
import rateLimit from "@fastify/rate-limit";
|
||||
import { healthRoutes } from "./routes/health.js";
|
||||
import { authRoutes } from "./routes/auth.js";
|
||||
|
||||
const app = Fastify({ logger: true });
|
||||
|
||||
await app.register(cors, { origin: true });
|
||||
await app.register(rateLimit, { max: 100, timeWindow: "1 minute" });
|
||||
await app.register(jwt, { secret: process.env.JWT_SECRET! });
|
||||
|
||||
app.decorate("authenticate", async (request: FastifyRequest, reply: FastifyReply) => {
|
||||
try {
|
||||
await request.jwtVerify();
|
||||
} catch {
|
||||
reply.code(401).send({ error: "Unauthorized" });
|
||||
}
|
||||
});
|
||||
|
||||
await app.register(healthRoutes);
|
||||
await app.register(authRoutes);
|
||||
|
||||
const port = Number(process.env.PORT ?? 4000);
|
||||
app.listen({ port, host: "0.0.0.0" }).catch((err) => {
|
||||
app.log.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import "fastify";
|
||||
import "@fastify/jwt";
|
||||
|
||||
declare module "fastify" {
|
||||
interface FastifyInstance {
|
||||
authenticate(request: FastifyRequest, reply: FastifyReply): Promise<void>;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "@fastify/jwt" {
|
||||
interface FastifyJWT {
|
||||
payload: { sub: string; email: string };
|
||||
user: { sub: string; email: string };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
# Product Requirements Document
|
||||
|
||||
> Filled out by `/requirements` in Init Mode, or manually before the first `/requirements` run. Keep every section concrete enough that a stranger could read it and know what NOT to build.
|
||||
|
||||
## Vision
|
||||
_Describe what you are building and why._
|
||||
_2-3 sentences: what is this product, who is it for, why does it need to exist. Not a feature list — the problem it solves._
|
||||
|
||||
## Target Users
|
||||
_Who will use this product? Describe their needs and pain points._
|
||||
_Name the actual user type(s), not "everyone". For each: their concrete pain point today, and what they're using instead of this product._
|
||||
|
||||
## Core Features (Roadmap)
|
||||
|
||||
@@ -15,14 +17,16 @@ _Who will use this product? Describe their needs and pain points._
|
||||
| P1 | _Feature 3_ | Planned |
|
||||
| P2 | _Feature 4_ | Planned |
|
||||
|
||||
P0 = cannot ship without it. P1 = next after MVP validates. P2 = explicitly deferred, not "maybe never."
|
||||
|
||||
## Success Metrics
|
||||
_How will you measure success? (e.g., user signups, retention, task completion rate)_
|
||||
_Concrete, measurable, with a number or threshold — not "users are happy". E.g. "50 signups in first month", "task completion rate > 80%"._
|
||||
|
||||
## Constraints
|
||||
_Budget, timeline, technical limitations, team size._
|
||||
_Real limits that shape decisions: timeline, budget, team size, must-run-on-existing-server, no third-party SaaS dependencies, etc._
|
||||
|
||||
## Non-Goals
|
||||
_What are you explicitly NOT building in this version?_
|
||||
_Explicitly out of scope for this version — the things people will ask for that you're deliberately not building yet, and why._
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -4,17 +4,20 @@
|
||||
|
||||
Create indexes on columns used in WHERE, ORDER BY, or JOIN clauses:
|
||||
|
||||
```sql
|
||||
-- Without index: ~500ms at 100k rows
|
||||
SELECT * FROM tasks WHERE user_id = 'abc123' ORDER BY created_at DESC;
|
||||
|
||||
-- After creating index: <10ms
|
||||
CREATE INDEX idx_tasks_user_id_created ON tasks(user_id, created_at DESC);
|
||||
```ts
|
||||
// backend/src/db/schema.ts
|
||||
export const tasks = pgTable("tasks", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
userId: uuid("user_id").notNull(),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
}, (table) => ({
|
||||
userCreatedIdx: index("idx_tasks_user_id_created").on(table.userId, table.createdAt.desc()),
|
||||
}));
|
||||
```
|
||||
|
||||
**Rule of thumb:** If a column appears in WHERE or ORDER BY and the table will have >1000 rows, add an index.
|
||||
|
||||
Always include indexes in your migration SQL alongside CREATE TABLE.
|
||||
Always include indexes in the schema alongside the table definition, so they ship with the same migration.
|
||||
|
||||
## 2. Avoid N+1 Queries
|
||||
|
||||
@@ -22,18 +25,21 @@ The most common performance problem with ORMs and query builders:
|
||||
|
||||
```typescript
|
||||
// Bad: N+1 (1 query for users + N queries for tasks)
|
||||
const { data: users } = await supabase.from('users').select('*')
|
||||
for (const user of users) {
|
||||
const { data: tasks } = await supabase
|
||||
.from('tasks')
|
||||
.select('*')
|
||||
.eq('user_id', user.id)
|
||||
const allUsers = await db.select().from(users);
|
||||
for (const user of allUsers) {
|
||||
const userTasks = await db.select().from(tasks).where(eq(tasks.userId, user.id));
|
||||
}
|
||||
|
||||
// Good: Single query with join (1 query total)
|
||||
const { data } = await supabase
|
||||
.from('users')
|
||||
.select('*, tasks(*)')
|
||||
// Good: single query with a join
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.leftJoin(tasks, eq(tasks.userId, users.id));
|
||||
|
||||
// Or with Drizzle's relational query API (if relations are defined)
|
||||
const usersWithTasks = await db.query.users.findMany({
|
||||
with: { tasks: true },
|
||||
});
|
||||
```
|
||||
|
||||
## 3. Always Limit Results
|
||||
@@ -41,34 +47,32 @@ const { data } = await supabase
|
||||
Never return unbounded results from the database:
|
||||
|
||||
```typescript
|
||||
// Bad: Returns ALL rows
|
||||
const { data } = await supabase.from('tasks').select('*')
|
||||
// Bad: returns ALL rows
|
||||
const rows = await db.select().from(tasks);
|
||||
|
||||
// Good: Returns max 50 rows
|
||||
const { data } = await supabase.from('tasks').select('*').limit(50)
|
||||
// Good: capped
|
||||
const rows = await db.select().from(tasks).limit(50);
|
||||
|
||||
// Better: Paginated
|
||||
const { data } = await supabase
|
||||
.from('tasks')
|
||||
.select('*')
|
||||
.range(0, 49) // First 50 rows
|
||||
// Better: paginated
|
||||
const rows = await db.select().from(tasks).limit(50).offset(page * 50);
|
||||
```
|
||||
|
||||
## 4. Caching Strategy
|
||||
|
||||
For data that changes rarely (dashboard stats, config, categories):
|
||||
For data that changes rarely (dashboard stats, config, categories), cache in the backend process (or a small in-memory LRU) rather than hitting Postgres on every request:
|
||||
|
||||
```typescript
|
||||
import { unstable_cache } from 'next/cache'
|
||||
let cachedCategories: Category[] | null = null;
|
||||
let cachedAt = 0;
|
||||
|
||||
export const getCategories = unstable_cache(
|
||||
async () => {
|
||||
const { data } = await supabase.from('categories').select('*')
|
||||
return data
|
||||
},
|
||||
['categories'], // Cache key
|
||||
{ revalidate: 3600 } // Refresh every hour
|
||||
)
|
||||
export async function getCategories() {
|
||||
if (cachedCategories && Date.now() - cachedAt < 3600_000) {
|
||||
return cachedCategories;
|
||||
}
|
||||
cachedCategories = await db.select().from(categories);
|
||||
cachedAt = Date.now();
|
||||
return cachedCategories;
|
||||
}
|
||||
```
|
||||
|
||||
**When to cache:**
|
||||
@@ -78,14 +82,14 @@ export const getCategories = unstable_cache(
|
||||
|
||||
**When NOT to cache:**
|
||||
- User-specific data that changes frequently
|
||||
- Real-time data (use Supabase Realtime instead)
|
||||
- Data that needs to be live across multiple backend instances (use a shared cache like Redis instead of in-process memory)
|
||||
|
||||
## 5. Select Only What You Need
|
||||
|
||||
```typescript
|
||||
// Bad: Fetches all columns
|
||||
const { data } = await supabase.from('users').select('*')
|
||||
// Bad: fetches all columns
|
||||
const rows = await db.select().from(users);
|
||||
|
||||
// Good: Fetches only needed columns
|
||||
const { data } = await supabase.from('users').select('id, name, avatar_url')
|
||||
// Good: fetches only needed columns
|
||||
const rows = await db.select({ id: users.id, email: users.email }).from(users);
|
||||
```
|
||||
@@ -1,32 +1,48 @@
|
||||
# Error Tracking Setup (Sentry)
|
||||
# Error Tracking Setup (Sentry, self-hosted friendly)
|
||||
|
||||
Track production errors automatically so you know about issues before your users report them.
|
||||
Track production errors automatically so you know about issues before your users report them. Sentry's SaaS free tier works fine even on a self-hosted app — it's just a DSN endpoint, no coupling to any specific host.
|
||||
|
||||
## Setup (5 minutes)
|
||||
|
||||
### 1. Create Sentry Account
|
||||
- Go to [sentry.io](https://sentry.io) (free tier available for small apps)
|
||||
- Create a new project and select "Next.js"
|
||||
- Create a project of type "Node" for the backend and one of type "React" for the frontend (or one combined project)
|
||||
|
||||
### 2. Install Next.js Integration
|
||||
### 2. Install SDKs
|
||||
```bash
|
||||
npx @sentry/wizard@latest -i nextjs
|
||||
```
|
||||
This automatically:
|
||||
- Installs `@sentry/nextjs`
|
||||
- Creates `sentry.client.config.ts` and `sentry.server.config.ts`
|
||||
- Updates `next.config.ts` with Sentry webpack plugin
|
||||
# backend/
|
||||
npm install @sentry/node
|
||||
|
||||
### 3. Add Environment Variables
|
||||
Add to `.env.local` (local) and Vercel Dashboard (production):
|
||||
# frontend/
|
||||
npm install @sentry/react
|
||||
```
|
||||
|
||||
### 3. Initialize
|
||||
|
||||
Backend (`backend/src/server.ts`, before anything else runs):
|
||||
```ts
|
||||
import * as Sentry from "@sentry/node";
|
||||
Sentry.init({ dsn: process.env.SENTRY_DSN });
|
||||
```
|
||||
|
||||
Frontend (`frontend/src/main.tsx`, before rendering):
|
||||
```ts
|
||||
import * as Sentry from "@sentry/react";
|
||||
Sentry.init({ dsn: import.meta.env.VITE_SENTRY_DSN });
|
||||
```
|
||||
|
||||
### 4. Add Environment Variables
|
||||
Backend `.env`:
|
||||
```bash
|
||||
SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/xxx
|
||||
NEXT_PUBLIC_SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/xxx
|
||||
SENTRY_AUTH_TOKEN=sntrys_xxx # For source maps upload
|
||||
```
|
||||
Frontend `.env` (baked in at build time, so rebuild after changing it):
|
||||
```bash
|
||||
VITE_SENTRY_DSN=https://xxx@xxx.ingest.sentry.io/xxx
|
||||
```
|
||||
|
||||
### 4. Verify Setup
|
||||
Trigger a test error and check Sentry Dashboard:
|
||||
### 5. Verify Setup
|
||||
Trigger a test error and check the Sentry Dashboard:
|
||||
```typescript
|
||||
// Temporary test - remove after verification
|
||||
throw new Error("Sentry test error")
|
||||
@@ -34,10 +50,10 @@ throw new Error("Sentry test error")
|
||||
|
||||
## What You Get
|
||||
- Automatic error capture (client + server)
|
||||
- Stack traces with source maps
|
||||
- Stack traces with source maps (upload via `@sentry/vite-plugin` / `@sentry/esbuild-plugin` in the build step)
|
||||
- Error grouping and deduplication
|
||||
- Email alerts for new errors
|
||||
- Performance monitoring (optional)
|
||||
|
||||
## Alternative
|
||||
**Vercel Error Tracking** - Built-in, simpler, but fewer features. Available in Vercel Dashboard under "Monitoring".
|
||||
Self-hosted **GlitchTip** — an open-source, Sentry-protocol-compatible error tracker you can run on your own server if sending errors to a third-party SaaS is not acceptable.
|
||||
@@ -15,21 +15,23 @@
|
||||
// Bad - unoptimized, no lazy loading
|
||||
<img src="/large-image.jpg" />
|
||||
|
||||
// Good - Next.js Image component
|
||||
import Image from 'next/image'
|
||||
<Image src="/large-image.jpg" width={800} height={600} alt="Description" />
|
||||
// Good - explicit dimensions + native lazy loading
|
||||
<img src="/large-image.jpg" width={800} height={600} loading="lazy" alt="Description" />
|
||||
```
|
||||
Next.js Image automatically: resizes, lazy-loads, serves WebP format.
|
||||
Pre-resize/compress source images (e.g. with `sharp` in a build step, or an image CDN) — Vite does not do this automatically like Next.js Image did.
|
||||
|
||||
### Large JavaScript Bundle
|
||||
Use dynamic imports for heavy components that aren't needed on initial load:
|
||||
```tsx
|
||||
import dynamic from 'next/dynamic'
|
||||
import { lazy, Suspense } from "react";
|
||||
|
||||
const HeavyChart = dynamic(() => import('./HeavyChart'), {
|
||||
loading: () => <p>Loading chart...</p>,
|
||||
})
|
||||
const HeavyChart = lazy(() => import("./HeavyChart"));
|
||||
|
||||
<Suspense fallback={<p>Loading chart...</p>}>
|
||||
<HeavyChart />
|
||||
</Suspense>
|
||||
```
|
||||
Vite code-splits dynamic imports automatically — check `npm run build`'s output chunk sizes.
|
||||
|
||||
### Missing Loading States
|
||||
Always show feedback during data fetching:
|
||||
@@ -41,27 +43,15 @@ if (isLoading) return <Skeleton className="h-12 w-full" />
|
||||
```
|
||||
|
||||
### No Caching Strategy
|
||||
Cache slow database queries with `unstable_cache`:
|
||||
```typescript
|
||||
import { unstable_cache } from 'next/cache'
|
||||
|
||||
export const getStats = unstable_cache(
|
||||
async () => {
|
||||
const { data } = await supabase.from('stats').select('*')
|
||||
return data
|
||||
},
|
||||
['dashboard-stats'],
|
||||
{ revalidate: 3600 } // Refresh every hour
|
||||
)
|
||||
```
|
||||
Cache slow backend queries in-process (see [database-optimization.md](database-optimization.md)) rather than re-querying Postgres on every request for rarely-changing data.
|
||||
|
||||
## Quick Wins Checklist
|
||||
- [ ] All images use `next/image` component
|
||||
- [ ] Heavy components use dynamic imports
|
||||
- [ ] Images have explicit width/height and `loading="lazy"` where below the fold
|
||||
- [ ] Heavy components use `React.lazy` + `Suspense`
|
||||
- [ ] Loading states show skeleton/spinner
|
||||
- [ ] Fonts loaded with `next/font`
|
||||
- [ ] No unnecessary client-side JavaScript (`"use client"` only when needed)
|
||||
- [ ] Fonts loaded via `@font-face`/Google Fonts `<link>`, with `font-display: swap`
|
||||
- [ ] Production build (`npm run build`) checked for unexpectedly large chunks
|
||||
|
||||
## Automated Monitoring
|
||||
- **Vercel Analytics** - Automatic on Pro plan, shows Core Web Vitals
|
||||
- **Vercel Speed Insights** - Real user performance data
|
||||
- Self-hosted **Plausible** or **Umami** for privacy-friendly traffic analytics
|
||||
- `web-vitals` npm package to report Core Web Vitals from real users to your own backend/logging
|
||||
@@ -1,92 +1,46 @@
|
||||
# Rate Limiting
|
||||
|
||||
Prevent abuse, DDoS attacks, and excessive API usage.
|
||||
Prevent abuse, brute-force, and excessive API usage.
|
||||
|
||||
## When to Add Rate Limiting
|
||||
- **MVP:** Optional (focus on features first)
|
||||
- **Production with users:** Recommended on auth endpoints and public APIs
|
||||
- **Public-facing APIs:** Required
|
||||
|
||||
## Setup with Upstash Redis
|
||||
## Setup with @fastify/rate-limit
|
||||
|
||||
### 1. Install Dependencies
|
||||
No external service needed — in-memory by default, works fine for a single backend instance.
|
||||
|
||||
### 1. Install Dependency
|
||||
```bash
|
||||
npm install @upstash/ratelimit @upstash/redis
|
||||
cd backend && npm install @fastify/rate-limit
|
||||
```
|
||||
|
||||
### 2. Create Upstash Account
|
||||
- Go to [upstash.com](https://upstash.com) (free tier: 10k requests/day)
|
||||
- Create a Redis database
|
||||
- Copy REST URL and token
|
||||
|
||||
### 3. Add Environment Variables
|
||||
```bash
|
||||
# .env.local
|
||||
UPSTASH_REDIS_REST_URL=https://xxx.upstash.io
|
||||
UPSTASH_REDIS_REST_TOKEN=xxx
|
||||
```
|
||||
|
||||
### 4. Create Rate Limiter
|
||||
### 2. Register Globally
|
||||
```typescript
|
||||
// src/lib/rate-limit.ts
|
||||
import { Ratelimit } from '@upstash/ratelimit'
|
||||
import { Redis } from '@upstash/redis'
|
||||
// backend/src/server.ts
|
||||
import rateLimit from "@fastify/rate-limit";
|
||||
|
||||
export const ratelimit = new Ratelimit({
|
||||
redis: Redis.fromEnv(),
|
||||
limiter: Ratelimit.slidingWindow(10, '10 s'), // 10 requests per 10 seconds
|
||||
})
|
||||
await app.register(rateLimit, {
|
||||
max: 100,
|
||||
timeWindow: "1 minute",
|
||||
});
|
||||
```
|
||||
|
||||
### 5. Use in API Routes
|
||||
### 3. Tighter Limits on Specific Routes
|
||||
```typescript
|
||||
// src/app/api/example/route.ts
|
||||
import { ratelimit } from '@/lib/rate-limit'
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const ip = request.headers.get('x-forwarded-for') ?? 'anonymous'
|
||||
const { success, limit, remaining } = await ratelimit.limit(ip)
|
||||
|
||||
if (!success) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Too many requests' },
|
||||
{
|
||||
status: 429,
|
||||
headers: {
|
||||
'X-RateLimit-Limit': limit.toString(),
|
||||
'X-RateLimit-Remaining': remaining.toString(),
|
||||
// backend/src/routes/auth.ts
|
||||
app.post("/api/auth/login", {
|
||||
config: {
|
||||
rateLimit: { max: 5, timeWindow: "1 minute" },
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Process request normally...
|
||||
}
|
||||
}, async (request, reply) => {
|
||||
// login handler
|
||||
});
|
||||
```
|
||||
|
||||
### 6. Use in Middleware (Global)
|
||||
```typescript
|
||||
// middleware.ts
|
||||
import { ratelimit } from '@/lib/rate-limit'
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
// Only rate limit API routes
|
||||
if (request.nextUrl.pathname.startsWith('/api/')) {
|
||||
const ip = request.headers.get('x-forwarded-for') ?? 'anonymous'
|
||||
const { success } = await ratelimit.limit(ip)
|
||||
|
||||
if (!success) {
|
||||
return NextResponse.json({ error: 'Too Many Requests' }, { status: 429 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: '/api/:path*',
|
||||
}
|
||||
```
|
||||
### 4. Response on Limit Exceeded
|
||||
`@fastify/rate-limit` returns HTTP 429 automatically with `Retry-After` and `X-RateLimit-*` headers — no manual handling needed.
|
||||
|
||||
## Recommended Limits
|
||||
|
||||
@@ -97,5 +51,5 @@ export const config = {
|
||||
| General API | 30 requests | 10 seconds |
|
||||
| File Upload | 5 requests | 1 minute |
|
||||
|
||||
## Alternative
|
||||
**Vercel Edge Config** - Simpler but less flexible. Built into Vercel, no external service needed.
|
||||
## Multi-Instance Deployments
|
||||
If the backend ever runs as more than one process/instance behind a load balancer, switch the plugin's store to Redis (`@fastify/rate-limit` supports a custom `redis` client option) so limits are shared across instances instead of per-process.
|
||||
@@ -4,41 +4,28 @@ Protect against XSS, Clickjacking, MIME sniffing, and other common web attacks.
|
||||
|
||||
## Setup
|
||||
|
||||
Add security headers to `next.config.ts`:
|
||||
Set security headers via the reverse proxy (nginx) in front of both frontend static files and the backend, or via `@fastify/helmet` on the backend directly. nginx is the simpler single place to cover everything.
|
||||
|
||||
```typescript
|
||||
import type { NextConfig } from 'next'
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: '/:path*',
|
||||
headers: [
|
||||
{
|
||||
key: 'X-Frame-Options',
|
||||
value: 'DENY',
|
||||
},
|
||||
{
|
||||
key: 'X-Content-Type-Options',
|
||||
value: 'nosniff',
|
||||
},
|
||||
{
|
||||
key: 'Referrer-Policy',
|
||||
value: 'origin-when-cross-origin',
|
||||
},
|
||||
{
|
||||
key: 'Strict-Transport-Security',
|
||||
value: 'max-age=31536000; includeSubDomains',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
### nginx (recommended — covers frontend + backend uniformly)
|
||||
```nginx
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "origin-when-cross-origin" always;
|
||||
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
```
|
||||
Add these inside the `server { }` block for the site.
|
||||
|
||||
### Alternative: @fastify/helmet on the backend
|
||||
```bash
|
||||
cd backend && npm install @fastify/helmet
|
||||
```
|
||||
```typescript
|
||||
// backend/src/server.ts
|
||||
import helmet from "@fastify/helmet";
|
||||
|
||||
await app.register(helmet);
|
||||
```
|
||||
Covers only API responses — still set the same headers in nginx for the static frontend files.
|
||||
|
||||
## What Each Header Does
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Feature Index
|
||||
|
||||
> Central tracking for all features. Updated by skills automatically.
|
||||
> Central tracking for all features. Updated by skills automatically — never hand-edit the status column without also updating the matching feature spec header.
|
||||
|
||||
## Status Legend
|
||||
- **Planned** - Requirements written, ready for development
|
||||
- **In Progress** - Currently being built
|
||||
- **Planned** - Requirements written, ready for architecture
|
||||
- **In Progress** - Tech design done, frontend/backend being built
|
||||
- **In Review** - QA testing in progress
|
||||
- **Deployed** - Live in production
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ Am Ende des Feature-Dokuments fügt QA die Test-Ergebnisse hinzu:
|
||||
|
||||
**Status:** ✅ Deployed
|
||||
**Deployed:** 2026-01-13
|
||||
**Production URL:** https://your-app.vercel.app
|
||||
**Production URL:** https://your-domain.example
|
||||
**Git Tag:** v1.0.0-PROJ-1
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Only VITE_-prefixed vars are exposed to the browser at build time.
|
||||
VITE_SENTRY_DSN=
|
||||
|
||||
# Add more environment variables as needed
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"$schema": "https://ui.shadcn.com/schema.json",
|
||||
"style": "default",
|
||||
"rsc": true,
|
||||
"rsc": false,
|
||||
"tsx": true,
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src/app/globals.css",
|
||||
"css": "src/index.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>AI Coding Starter Kit</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "ai-coding-starter-kit",
|
||||
"name": "ai-coding-starter-kit-frontend",
|
||||
"version": "1.0.0",
|
||||
"description": "AI Coding Starter Kit with AI Agent Team System for Claude Code",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
@@ -31,16 +31,15 @@
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-toast": "^1.2.15",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@supabase/supabase-js": "^2.39.3",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.0",
|
||||
"cmdk": "^1.1.1",
|
||||
"lucide-react": "^0.562.0",
|
||||
"next": "^16.1.1",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-hook-form": "^7.71.1",
|
||||
"react-router-dom": "^7.1.1",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^2.2.0",
|
||||
"zod": "^4.3.5"
|
||||
@@ -49,11 +48,12 @@
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.1.1",
|
||||
"postcss": "^8",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"typescript": "^5"
|
||||
"typescript": "^5",
|
||||
"vite": "^6.0.7"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 233 B After Width: | Height: | Size: 233 B |
|
Before Width: | Height: | Size: 303 B After Width: | Height: | Size: 303 B |
|
Before Width: | Height: | Size: 206 B After Width: | Height: | Size: 206 B |
@@ -0,0 +1,14 @@
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
<main className="min-h-screen flex items-center justify-center">
|
||||
<h1 className="text-2xl font-semibold">AI Coding Starter Kit</h1>
|
||||
</main>
|
||||
<Toaster />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { type DialogProps } from "@radix-ui/react-dialog"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
CircleCheck,
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as SwitchPrimitives from "@radix-ui/react-switch"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as ToastPrimitives from "@radix-ui/react-toast"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import { useToast } from "@/hooks/use-toast"
|
||||
import {
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||
@@ -1,4 +1,3 @@
|
||||
"use client"
|
||||
|
||||
// Inspired by react-hot-toast library
|
||||
import * as React from "react"
|
||||
@@ -0,0 +1,10 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "./index.css";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
@@ -3,9 +3,8 @@ import type { Config } from "tailwindcss";
|
||||
const config: Config = {
|
||||
darkMode: ["class"],
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import path from "path";
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
"/api": "http://localhost:4000",
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6.2c.3 0 .4.1.4.4v12.9c0 .3-.1.4-.4.4h-6.2c-.3 0-.4-.1-.4-.4V45.8c0-.3.1-.4.4-.4zm-22 0h6.2c.3 0 .5.1.5.4v12.9c0 .3-.2.4-.5.4h-6.2c-.3 0-.4-.1-.4-.4V45.8c0-.3.1-.4.4-.4zm-11.4 0h6.2c.3 0 .5.1.5.4v12.9c0 .3-.2.4-.5.4h-6.2c-.3 0-.4-.1-.4-.4V45.8c0-.3.1-.4.4-.4zm-11.4 0h6.2c.3 0 .4.1.4.4v12.9c0 .3-.1.4-.4.4h-6.2c-.3 0-.4-.1-.4-.4V45.8c0-.3.1-.4.4-.4z"/><path fill="url(#a)" d="m317.2 78.9-.4 1.2h.4l21.1-27.3-21.1-27.3h-.4l.4 1.2a50 50 0 0 1 2.3 14.8 50 50 0 0 1-2.3 14.8l21.1 27.3h.4l-.4-1.2a50 50 0 0 0 2.3-14.8 50 50 0 0 0-2.3-14.8l.4-1.2h-.4l-21.1 27.3z"/><defs><linearGradient id="a" x1="267.5" x2="342.8" y1="27.8" y2="27.8" gradientUnits="userSpaceOnUse"><stop/><stop offset="1" stop-opacity="0"/></linearGradient></defs></svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
||||
|
Before Width: | Height: | Size: 630 B |
@@ -1,21 +0,0 @@
|
||||
import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "AI Coding Starter Kit",
|
||||
description: "Built with AI Agent Team System",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className="antialiased">
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
import Image from 'next/image'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/next.svg"
|
||||
alt="Next.js logo"
|
||||
width={180}
|
||||
height={38}
|
||||
priority
|
||||
/>
|
||||
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
|
||||
<li className="mb-2">
|
||||
Get started by editing{' '}
|
||||
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
|
||||
src/app/page.tsx
|
||||
</code>
|
||||
.
|
||||
</li>
|
||||
<li>Save and see your changes instantly.</li>
|
||||
</ol>
|
||||
|
||||
<div className="flex gap-4 items-center flex-col sm:flex-row">
|
||||
<a
|
||||
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
className="dark:invert"
|
||||
src="/vercel.svg"
|
||||
alt="Vercel logomark"
|
||||
width={20}
|
||||
height={20}
|
||||
/>
|
||||
Deploy now
|
||||
</a>
|
||||
<a
|
||||
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
|
||||
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Read our docs
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/file.svg"
|
||||
alt="File icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Learn
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/window.svg"
|
||||
alt="Window icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Examples
|
||||
</a>
|
||||
<a
|
||||
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
|
||||
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Image
|
||||
aria-hidden
|
||||
src="/globe.svg"
|
||||
alt="Globe icon"
|
||||
width={16}
|
||||
height={16}
|
||||
/>
|
||||
Go to nextjs.org →
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
// Supabase Client Setup
|
||||
// Uncomment this file when you're ready to use Supabase
|
||||
|
||||
/*
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
|
||||
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseAnonKey)
|
||||
*/
|
||||
|
||||
// For now, export a placeholder to avoid import errors
|
||||
export const supabase = null;
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||