- Use ESM for building the MCP Server - Added own Postgres dependency to MCP Server for querying tables and other entities in MCP - Vastly improved AI Agent rules - Added MCP Prompts for reviewing code and planning features - Minor refactoring
330 lines
8.5 KiB
Markdown
330 lines
8.5 KiB
Markdown
# Web Application Instructions
|
|
|
|
This file contains instructions specific to the main Next.js web application.
|
|
|
|
## Application Structure
|
|
|
|
### Route Organization
|
|
|
|
```
|
|
app/
|
|
├── (marketing)/ # Public pages (landing, blog, docs)
|
|
├── (auth)/ # Authentication pages
|
|
├── home/
|
|
│ ├── (user)/ # Personal account context
|
|
│ └── [account]/ # Team account context ([account] = team slug)
|
|
├── admin/ # Super admin section
|
|
└── api/ # API routes
|
|
```
|
|
|
|
Key Examples:
|
|
|
|
- Marketing layout: `app/(marketing)/layout.tsx`
|
|
- Personal dashboard: `app/home/(user)/page.tsx`
|
|
- Team workspace: `app/home/[account]/page.tsx`
|
|
- Admin section: `app/admin/page.tsx`
|
|
|
|
### Component Organization
|
|
|
|
- **Route-specific**: Use `_components/` directories
|
|
- **Route utilities**: Use `_lib/` for client, `_lib/server/` for server-side
|
|
- **Global components**: Root-level directories
|
|
|
|
Example:
|
|
|
|
- Team components: `app/home/[account]/_components/`
|
|
- Team server utils: `app/home/[account]/_lib/server/`
|
|
- Marketing components: `app/(marketing)/_components/`
|
|
|
|
The `[account]` parameter is the `accounts.slug` property, not the ID
|
|
|
|
## React Server Components - Async Pattern
|
|
|
|
**CRITICAL**: In Next.js 15, always await params directly in async server components:
|
|
|
|
```typescript
|
|
// ❌ WRONG - Don't use React.use() in async functions
|
|
async function Page({ params }: Props) {
|
|
const { account } = use(params);
|
|
}
|
|
|
|
// ✅ CORRECT - await params directly in Next.js 15
|
|
async function Page({ params }: Props) {
|
|
const { account } = await params; // ✅ Server component pattern
|
|
}
|
|
|
|
// ✅ CORRECT - "use" in non-async functions in Next.js 15
|
|
function Page({ params }: Props) {
|
|
const { account } = use(params); // ✅ Server component pattern
|
|
}
|
|
```
|
|
|
|
## Data Fetching Strategy
|
|
|
|
**Quick Decision Framework:**
|
|
|
|
- **Server Components**: Default choice for initial data loading
|
|
- **Client Components**: For interactive features requiring hooks or real-time updates
|
|
- **Admin Client**: Only for bypassing RLS (rare cases - requires manual auth/authorization)
|
|
|
|
### Server Components (Preferred) ✅
|
|
|
|
```typescript
|
|
import { getSupabaseServerClient } from '@kit/supabase/server-client';
|
|
|
|
async function NotesPage() {
|
|
const client = getSupabaseServerClient();
|
|
const { data, error } = await client.from('notes').select('*');
|
|
|
|
if (error) return <ErrorMessage error={error} />;
|
|
return <NotesList notes={data} />;
|
|
}
|
|
```
|
|
|
|
**Key Insight**: Server Components automatically inherit RLS protection - no additional authorization checks needed!
|
|
|
|
### Client Components (Interactive) 🖱️
|
|
|
|
```typescript
|
|
'use client';
|
|
import { useSupabase } from '@kit/supabase/hooks/use-supabase';
|
|
import { useQuery } from '@tanstack/react-query';
|
|
|
|
function InteractiveNotes() {
|
|
const supabase = useSupabase();
|
|
const { data, isLoading } = useQuery({
|
|
queryKey: ['notes'],
|
|
queryFn: () => supabase.from('notes').select('*')
|
|
});
|
|
|
|
if (isLoading) return <Spinner />;
|
|
return <NotesList notes={data} />;
|
|
}
|
|
```
|
|
|
|
### Performance Optimization - Parallel Data Fetching 🚀
|
|
|
|
**Sequential (Slow) Pattern ❌**
|
|
|
|
```typescript
|
|
async function SlowDashboard() {
|
|
const userData = await loadUserData();
|
|
const notifications = await loadNotifications();
|
|
const metrics = await loadMetrics();
|
|
// Total time: sum of all requests
|
|
}
|
|
```
|
|
|
|
**Parallel (Optimized) Pattern ✅**
|
|
|
|
```typescript
|
|
async function FastDashboard() {
|
|
// Execute all requests simultaneously
|
|
const [userData, notifications, metrics] = await Promise.all([
|
|
loadUserData(),
|
|
loadNotifications(),
|
|
loadMetrics()
|
|
]);
|
|
// Total time: longest single request
|
|
|
|
return <Dashboard user={userData} notifications={notifications} metrics={metrics} />;
|
|
}
|
|
```
|
|
|
|
**Performance Impact**: Parallel fetching can reduce page load time by 60-80% for multi-data pages!
|
|
|
|
## Authorization Patterns - Critical Understanding 🔐
|
|
|
|
### RLS-Protected Data Fetching (Standard) ✅
|
|
|
|
```typescript
|
|
async function getUserNotes(userId: string) {
|
|
const client = getSupabaseServerClient();
|
|
|
|
// RLS automatically ensures user can only access their own notes
|
|
// NO additional authorization checks needed!
|
|
const { data } = await client.from('notes').select('*').eq('user_id', userId); // RLS validates this automatically
|
|
|
|
return data;
|
|
}
|
|
```
|
|
|
|
### Admin Client Usage (Dangerous - Rare Cases Only) ⚠️
|
|
|
|
```typescript
|
|
async function adminGetUserNotes(userId: string) {
|
|
const adminClient = getSupabaseServerAdminClient();
|
|
|
|
// CRITICAL: Manual authorization required - bypasses RLS!
|
|
const currentUser = await getCurrentUser();
|
|
if (!(await isSuperAdmin(currentUser))) {
|
|
throw new Error('Unauthorized: Admin access required');
|
|
}
|
|
|
|
// Additional validation: ensure current admin isn't targeting themselves
|
|
if (currentUser.id === userId) {
|
|
throw new Error('Cannot perform admin action on own account');
|
|
}
|
|
|
|
// Now safe to proceed with admin privileges
|
|
const { data } = await adminClient
|
|
.from('notes')
|
|
.select('*')
|
|
.eq('user_id', userId);
|
|
|
|
return data;
|
|
}
|
|
```
|
|
|
|
**Rule of thumb**: If using standard Supabase client, trust RLS. If using admin client, validate everything manually.
|
|
|
|
## Internationalization
|
|
|
|
Always use `Trans` component from `@kit/ui/trans`:
|
|
|
|
```tsx
|
|
import { Trans } from '@kit/ui/trans';
|
|
|
|
<Trans
|
|
i18nKey="user:welcomeMessage"
|
|
values={{ name: user.name }}
|
|
/>
|
|
|
|
// With HTML elements
|
|
<Trans
|
|
i18nKey="terms:agreement"
|
|
components={{
|
|
TermsLink: <a href="/terms" className="underline" />,
|
|
}}
|
|
/>
|
|
```
|
|
|
|
### Adding New Languages
|
|
|
|
1. Add language code to `lib/i18n/i18n.settings.ts`
|
|
2. Create translation files in `public/locales/[new-language]/`
|
|
3. Copy structure from English files
|
|
|
|
### Adding new namespaces
|
|
|
|
1. Translation files: `public/locales/<locale>/<namespace>.json`
|
|
2. Add namespace to `defaultI18nNamespaces` in `apps/web/lib/i18n/i18n.settings.ts`
|
|
|
|
## Workspace Contexts 🏢
|
|
|
|
### Personal Account Context (`app/home/(user)`)
|
|
|
|
```tsx
|
|
import { useUserWorkspace } from '@kit/accounts/hooks/use-user-workspace';
|
|
|
|
function PersonalComponent() {
|
|
const { user, account } = useUserWorkspace();
|
|
// Personal account data
|
|
}
|
|
```
|
|
|
|
Context provider: `@packages/features/accounts/src/components/user-workspace-context-provider.tsx`
|
|
|
|
### Team Account Context (`app/home/[account]`)
|
|
|
|
```tsx
|
|
import { useTeamAccountWorkspace } from '@kit/team-accounts/hooks/use-team-account-workspace';
|
|
|
|
function TeamComponent() {
|
|
const { account, user, accounts } = useTeamAccountWorkspace();
|
|
// Team account data with permissions
|
|
}
|
|
```
|
|
|
|
Context provider: `@packages/features/team-accounts/src/components/team-account-workspace-context-provider.tsx`
|
|
|
|
## Key Configuration Files
|
|
|
|
- **Feature flags**: `config/feature-flags.config.ts`
|
|
- **i18n settings**: `lib/i18n/i18n.settings.ts`
|
|
- **Supabase config**: `supabase/config.toml`
|
|
- **Middleware**: `middleware.ts`
|
|
|
|
## Route Handlers (API Routes)
|
|
|
|
Use `enhanceRouteHandler` from `@packages/next/src/routes/index.ts`:
|
|
|
|
```typescript
|
|
import { enhanceRouteHandler } from '@kit/next/routes';
|
|
|
|
export const POST = enhanceRouteHandler(
|
|
async function ({ body, user, request }) {
|
|
// body is validated, user available if auth: true
|
|
return NextResponse.json({ success: true });
|
|
},
|
|
{
|
|
auth: true,
|
|
schema: ZodSchema,
|
|
},
|
|
);
|
|
```
|
|
|
|
## Navigation Menu Configuration 🗺️
|
|
|
|
### Adding Sidebar Menu Items
|
|
|
|
**Config Files:**
|
|
|
|
- Personal: `config/personal-account-navigation.config.tsx`
|
|
- Team: `config/team-account-navigation.config.tsx`
|
|
|
|
**Add to Personal Navigation:**
|
|
|
|
```typescript
|
|
{
|
|
label: 'common:routes.yourFeature',
|
|
path: pathsConfig.app.yourFeaturePath,
|
|
Icon: <YourIcon className="w-4" />,
|
|
end: true,
|
|
},
|
|
```
|
|
|
|
**Add to Team Navigation:**
|
|
|
|
```typescript
|
|
{
|
|
label: 'common:routes.yourTeamFeature',
|
|
path: createPath(pathsConfig.app.yourTeamFeaturePath, account),
|
|
Icon: <YourIcon className="w-4" />,
|
|
},
|
|
```
|
|
|
|
**Add Paths:**
|
|
|
|
```typescript
|
|
// config/paths.config.ts
|
|
app: {
|
|
yourFeaturePath: '/home/your-feature',
|
|
yourTeamFeaturePath: '/home/[account]/your-feature',
|
|
}
|
|
```
|
|
|
|
**Add Translations:**
|
|
|
|
```json
|
|
// public/locales/en/common.json
|
|
"routes": {
|
|
"yourFeature": "Your Feature"
|
|
}
|
|
```
|
|
|
|
## Security Guidelines 🛡️
|
|
|
|
### Authentication & Authorization
|
|
|
|
- Authentication already enforced by middleware
|
|
- Authorization handled by RLS at database level (in most cases)
|
|
- Avoid defensive code - use RLS instead
|
|
- When using the Supabase admin client, must enforce both authentication and authorization
|
|
|
|
### Passing data to the client
|
|
|
|
- **Never pass sensitive data** to Client Components
|
|
- **Never expose server environment variables** to client (unless prefixed with NEXT_PUBLIC)
|
|
- Always validate user input
|