This is a Next.js monorepo project built with TypeScript, using modern web development practices and tools.
- pnpm is the package manager for this project
- Always use
pnpmcommands instead ofnpmoryarn - Workspaces are managed through pnpm workspace configuration
- Next.js - React framework with App Router
- TypeScript - Primary language for type safety
- React - UI library
- Supabase - Backend as a Service (BaaS)
- Authentication
- PostgreSQL database
- Real-time subscriptions
- Storage
- Edge Functions
- React Hook Form - Form state management
- Zod - Schema validation and type inference
- Always use Zod schemas for form validation
- Leverage type inference from Zod schemas
- shadcn/ui - Component registry (not a component library)
- Components are copied into the project and can be customized
- Typically located in
@/components/ui - Built on top of Radix UI primitives
- Styled with Tailwind CSS
- Tailwind CSS - Utility-first CSS framework
- Follow Tailwind conventions and use utility classes
- Prefer
typeoverinterfacefor type definitions - Use proper TypeScript types throughout the codebase
- Avoid
anytype - only use in inevitable situations where proper typing is genuinely impossible - Use type inference where possible
- Leverage utility types (
Partial,Pick,Omit,Record, etc.)
Example:
// Preferred ✅
type User = {
id: string;
name: string;
email: string;
};
type UserFormData = Pick<User, 'name' | 'email'>;
// Avoid ❌
interface User {
id: string;
name: string;
email: string;
}
const data: any = fetchData(); // Avoid this- Prefer
functiondeclarations overconstarrow functions where appropriate - Use function declarations for top-level functions, utilities, and standalone functions
- Arrow functions are acceptable for callbacks, inline functions, and when lexical
thisbinding is needed - React components can use either pattern, but be consistent within the codebase
// Preferred for top-level/utility functions ✅
function calculateTotal(items: Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0);
}
function formatUser(user: User): string {
return `${user.name} (${user.email})`;
}
// Acceptable for callbacks and inline functions ✅
const numbers = [1, 2, 3].map((n) => n * 2);
button.addEventListener('click', (e) => {
handleClick(e);
});
// Avoid for standalone functions ❌
const calculateTotal = (items: Item[]): number => {
return items.reduce((sum, item) => sum + item.price, 0);
};Benefits of function declarations:
- Hoisted (can be called before declaration)
- More readable for complex functions
- Better stack traces in debugging
- Clear intent for reusable functions
When to use arrow functions:
- Array methods (map, filter, reduce, etc.)
- Event handlers and callbacks
- When you need lexical
thisbinding - Short, inline operations
- Inside React components for event handlers
Use React Hook Form with Zod for all forms:
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const formSchema = z.object({
email: z.string().email(),
password: z.string().min(8),
});
type FormData = z.infer<typeof formSchema>;
function MyForm() {
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: {
email: '',
password: '',
},
});
// Handler functions can use arrow syntax for lexical this
const onSubmit = (data: FormData) => {
// Handle form submission
};
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* Form fields */}
</form>
);
}- Use functional components with hooks
- Follow React Server Components patterns for Next.js App Router
- Distinguish between Server Components and Client Components
- Use
"use client"directive only when necessary - Component functions can use either
functionor arrow function syntax (be consistent)
// Both styles are acceptable for React components
// Function declaration style
export function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
return <div>{user?.name}</div>;
}
// Or arrow function style (pick one and be consistent)
export const UserProfile = ({ userId }: { userId: string }) => {
const [user, setUser] = useState<User | null>(null);
return <div>{user?.name}</div>;
};- Use Supabase client for database operations
- Implement Row Level Security (RLS) policies
- Use TypeScript types generated from Supabase schema when possible
- Follow Supabase best practices for authentication and data fetching
Example:
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
import type { Database } from '@/types/supabase';
const supabase = createClientComponentClient<Database>();
type Profile = Database['public']['Tables']['profiles']['Row'];
// Use function declarations for data fetching utilities
async function fetchUserProfile(userId: string): Promise<Profile | null> {
const { data, error } = await supabase.from('profiles').select('*').eq('id', userId).single();
if (error) {
console.error('Error fetching profile:', error);
return null;
}
return data;
}.
├── apps/
│ └── web/ # Next.js application
│ ├── app/ # App Router pages and layouts
│ ├── components/ # React components
│ │ └── ui/ # shadcn/ui components
│ ├── lib/ # Utility functions and shared code
│ ├── types/ # TypeScript type definitions
│ └── public/ # Static assets
├── packages/ # Shared packages (if applicable)
├── package.json
├── pnpm-workspace.yaml
└── turbo.json # Turborepo config (if using)
# Install dependencies
pnpm install
# Development
pnpm dev
# Build
pnpm build
# Type checking
pnpm type-check
# Linting
pnpm lint-
Type Safety
- Always define proper types for function parameters and return values
- Use Zod for runtime validation and type inference
- Avoid type assertions unless absolutely necessary
-
Functions
- Use
functiondeclarations for standalone, reusable functions - Use arrow functions for callbacks, array methods, and when lexical
thisis needed - Be consistent with React component declaration style across the codebase
- Use
-
Forms
- Use React Hook Form for all form state management
- Define Zod schemas for validation
- Infer TypeScript types from Zod schemas using
z.infer<>
-
Component Design
- Keep components small and focused
- Use composition over configuration
- Leverage shadcn/ui components as building blocks
-
Error Handling
- Implement proper error boundaries
- Handle async errors appropriately
- Provide meaningful error messages to users
- Always use TypeScript with proper typing
- Use
typedeclarations instead ofinterface - Use
functiondeclarations for standalone functions and utilities - Use arrow functions appropriately (callbacks, array methods, lexical this)
- Ensure pnpm compatibility in any package.json suggestions
- Follow React Hook Form + Zod patterns for forms
- Use shadcn/ui components where applicable
- Consider Next.js App Router patterns (Server vs Client Components)
- Include Supabase integration patterns when dealing with data
- Avoid using
anytype - provide specific types or use generics
- When creating new components, check if shadcn/ui has a suitable component first
- When working with forms, always scaffold with React Hook Form + Zod
- When suggesting database operations, consider Supabase RLS and best practices
- Respect the monorepo structure and shared package conventions
- Always prefer type inference over explicit typing where TypeScript can infer correctly
- Use function declarations for utility functions, helpers, and business logic
- Use arrow functions for event handlers, callbacks, and array operations