These rules describe how to work in this repo using Cursor: file layout, React Server Components boundaries, where actions/forms live, and UI/data patterns.
## Cursor rules for react-router-dashboard-saas-template
These rules describe how to work in this repo using Cursor: file layout, React Server Components boundaries, where actions/forms live, and UI/data patterns.
### Stack overview
- React 19 + React Router 7 (RSC enabled via `@vitejs/plugin-rsc`)
- Server rendering entry at `src/entry.rsc.tsx`; SSR HTML generated by `src/entry.ssr.tsx`
- Tailwind CSS v4 + daisyUI v5 (semantic component classes)
- Drizzle ORM (PostgreSQL) with `drizzle-kit` migrations
- Conform (`@conform-to/react`) + `zod/v4` for form validation
- Session and request-scoped context via `AsyncLocalStorage`
### Environment
- Required env: `DATABASE_URL`, `SESSION_SECRET`
- Start: `pnpm dev`. Build: `pnpm build`. Typecheck: `pnpm typecheck`. DB: `pnpm db`, `pnpm db:generate`, `pnpm db:migrate`, `pnpm db:studio`.
### Directory layout and conventions
- `src/routes/`
- Each route folder typically contains:
- `route.tsx` → React Server Component (RSC) entry for the route.
- `client.tsx` → co-located Client Components for that route ("islands"). Use `"use client"` pragma.
- `client-on.ts` → small client helpers for imperative UI interactions (e.g., opening modals).
- Optional `handle.ts` (see `src/routes/app/handle.ts`) to expose server-only pieces via the `createHandle` pattern.
- Root shells:
- `src/routes/root/route.tsx` (layout shell + error boundary export from client)
- `src/routes/marketing/route.tsx` (public shell)
- `src/routes/app/route.tsx` (authenticated app shell)
- `src/actions/<domain>/`
- `actions.ts` → server actions (must start with `"use server"`).
- `schema.ts` → `zod/v4` schemas used by the actions and forms.
- Existing domains: `auth`, `organization`, `invitation`, `profile`.
- `src/components/` → shared components across routes.
- `@/components/form` → Conform + zod v4 opinionated wrappers (`useForm`, `Form`, `Input`, etc.).
- `src/components/ui/` → low-level UI primitives (`card`, `modal`, etc.).
- `src/db/`
- `schema.ts` → Drizzle table definitions.
- `queries/*` and `mutations/*` → data access layer separated by read/write.
- `index.ts` → `getDb()` pool + drizzle instance (reused via global).
- `src/lib/`
- `session.ts` → cookie session with ALS; use `getSession()`, `destroySession()`.
- `auth.ts` → `getUser()`, `requireUser()`, and route `unstable_middleware` helpers.
- `cache.ts` → response caching and dataloader batching.
### React Server Components model
- Route files (`route.tsx`) are server by default. Do data fetching here (DB, session, etc.). Avoid DB access in client components.
- Client components must opt-in with `"use client"` and should live in `client.tsx` (or adjacent files) within the route directory.
- Pass server-only values to client via props; avoid leaking server functions or DB clients.
- Use the `createHandle`/`getServerHandle` pattern when a route needs to expose server-only components to a parent shell (see `src/routes/app/handle.ts`).
- Route middleware: export `unstable_middleware` from the server route module to protect or redirect (see `redirectIfLoggedInMiddleware`, `requireUserMiddleware`).
- Caching: call `cacheRoute()` at the top of server route components when appropriate to set CDN/edge cache headers.
### Forms + actions pattern (Conform + zod/v4)
- Always use `zod/v4` and `@conform-to/zod/v4` to match the configured version.
- Example imports:
- `import { z } from "zod/v4"`
- `import { parseWithZod } from "@conform-to/zod/v4"`
- Server action signature:
- In `src/actions/<domain>/actions.ts` define actions with `"use server"` and the shape
`(prev: SubmissionResult | undefined, formData: FormData) => Promise<SubmissionResult>`.
- Parse on the server: `const submission = parseWithZod(formData, { schema })`.
- On validation failure: `return submission.reply({ ...options })`.
- On success: perform side effects, optionally `redirect(...)`, and return `submission.reply({ resetForm: boolean })`.
- Use `requireUser()` inside actions that need auth.
- Client form usage (see `src/routes/marketing/login/client.tsx`):
- Wire the server action using React 19’s `useActionState`:
`const [lastResult, action, pending] = useActionState(serverAction, undefined)`.
- Initialize Conform via our wrapper: `const [form, fields] = useForm({ action, lastResult, schema })`.
- Render with `<Form action={action} form={form}>` and use `<Input field={fields.email} ... />` etc.
- Use `<FormErrors form={form} />` and `<FormSuccessMessage lastResult={lastResult}>...</FormSuccessMessage>`.
- Keep redirect/query state in hidden inputs when needed (e.g., `redirectTo`).
- Where to put schemas:
- Define schemas in `src/actions/<domain>/schema.ts` and import into both the action and the client form.
- Naming:
- Existing naming is mixed (`login`, `signup`, `createOrganizationAction`, `updateName`, etc.). Prefer descriptive verbs; suffix with `Action` when it clarifies intent, but keep consistency within a domain.
### Data access (Drizzle)
- Define tables in `src/db/schema.ts`.
- Get a DB instance via `getDb()`; do not instantiate pools in actions/components.
- Separate reads/writes: put read functions in `src/db/queries/*` and mutations in `src/db/mutations/*`.
- Keep transactions and authorization checks in server actions or server route loaders (not in client).
- Generate and run migrations with drizzle-kit scripts.
### Auth and session
- Use `getUser()` to read current user (may be `undefined`). Use `requireUser()` when user must exist.
- For route-level protection, add `unstable_middleware = [requireUserMiddleware]` to `route.tsx`.
- Login/signup set the session via `getSession().set("user", { id })`; logout destroys it via `destroySession()`.
- Redirect using React Router’s `redirect()` only from server contexts.
### Routing specifics
- The route shells use daisyUI and Tailwind for layout (`navbar`, `drawer`, etc.).
- The marketing shell (`src/routes/marketing/route.tsx`) shows how to open auth modals from the navbar using `client-on.ts` helpers.
- The app shell (`src/routes/app/route.tsx`) demonstrates co-locating a server-provided `SidebarContent` via the handle API and rendering notifications fed by server data.
### UI and styling
- Tailwind v4 with daisyUI v5 (semantic-first):
- Prefer daisyUI component classes (e.g., `btn`, `input`, `card`) and semantic colors (`primary`, `base-100`, etc.).
- Use container pattern `max-w-screen-xl mx-auto px-4` for page shells.
- Use responsive helpers (`sm:menu-horizontal`, `lg:drawer-open`).
- Shared UI primitives live under `src/components/ui/*`. Add new ones here if broadly reusable. Route-specific UI belongs next to the route in `client.tsx` or sibling files.
### Adding a new feature (checklist)
1. Routing: create a new folder in `src/routes/...`, add `route.tsx` (server). If interactive UI is needed, add `client.tsx` with `"use client"` and render it from the server route.
2. Data: add query/mutation helpers in `src/db/queries/*` / `src/db/mutations/*` as needed. Reuse `getDb()`.
3. Actions: create `src/actions/<domain>/{schema.ts,actions.ts}` with zod v4 schemas and server actions.
4. Form: in the route’s `client.tsx`, use `useActionState`, `useForm({ action, lastResult, schema })`, and `<Form>`/`<Input>` components.
5. Auth: add `unstable_middleware` to routes requiring auth, and call `requireUser()` inside actions.
6. Caching: call `cacheRoute()` in server `route.tsx` if the page can be cached; avoid for highly dynamic/authenticated content.
7. Styling: use daisyUI components and semantic colors; keep shared primitives in `src/components/ui/*`.
### TypeScript and code style
- TS is strict; avoid `any`. Export explicit types for public helpers.
- Use the `@/*` path alias from `tsconfig.json`.
- Keep functions small and descriptive; prefer early returns; handle errors close to where they occur.
### Do’s and Don’ts
- Do parse and validate all form input on the server with zod v4.
- Do keep business logic in server actions or server route modules, not in client components.
- Don’t mutate session after response generation; use helpers from `src/lib/session.ts` within the server request lifecycle.
- Don’t access the DB from client code.
- Don’t bypass Conform’s `submission.reply` pattern; it powers `lastResult` and error display.
Workflows from the Neura Market marketplace related to this Cursor resource