Quick Reference Guide
Quick reference for common patterns, commands, and workflows in the SaaS platform
This is a quick reference guide for developers working on the platform. Bookmark this page for quick access to common patterns and commands.
Essential Commands
Development
# Start all apps pnpm dev # Start specific app pnpm --filter web dev # Type check pnpm typecheck # Lint and fix pnpm lint:fix # Format code pnpm format:fix
Database
# Start Supabase locally pnpm supabase:web:start # Apply migrations pnpm --filter web supabase migrations up # Reset database (clean rebuild) pnpm supabase:web:reset # Generate TypeScript types pnpm supabase:web:typegen # Create new migration pnpm --filter web supabase:db:diff
Common Patterns
1. Creating a Page
// apps/web/app/home/[account]/feature/page.tsx
import { withI18n } from '~/lib/i18n/with-i18n';
async function FeaturePage({ params }: Props) {
const { account } = await params;
const data = await loadFeatureData(account);
return (
<>
<TeamAccountLayoutPageHeader account={account} title={<Trans i18nKey="common:routes.feature" />}
description={
<AppBreadcrumbs />}
/>
<PageBody>
<FeatureContent data={data} />
</PageBody>
</>
);
}
export default withI18n(FeaturePage);
2. Creating a Server Action
// _lib/server/server-actions.ts
'use server';
import { enhanceAction } from '@kit/next/actions';
import { z } from 'zod';
const Schema = z.object({
name: z.string().min(1),
description: z.string().optional(),
});
export const createFeature = enhanceAction(
async (data) => {
const client = getSupabaseServerClient();
const { error } = await client
.from('features')
.insert(data);
if (error) throw error;
revalidatePath('/path');
return { success: true };
},
{ schema: Schema }
);
3. Creating a Loader
// _lib/server/feature-page.loader.ts
import 'server-only';
export async function loadFeaturePageData(
client: SupabaseClient,
id: string
) {
return Promise.all([
loadFeature(client, id),
loadRelatedData(client, id),
]);
}
4. Form with Validation
'use client';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
const form = useForm({
resolver: zodResolver(Schema),
defaultValues: { ... },
});
const onSubmit = form.handleSubmit((data) => {
startTransition(async () => {
const result = await serverAction(data);
if (result.success) {
toast.success('Success!');
}
});
});
<Form {...form}>
<form onSubmit={onSubmit}>
<FormField name="name" ... />
<Button type="submit">Submit</Button>
</form>
</Form>
5. Slug Generation with Validation
// Hook setup
const slugValidation = useSlugValidation({
accountId: props.account.id,
currentSlug: props.account.slug,
type: 'account',
validateFn: async (slug, options) => {
return validateAccountSlug({
slug,
accountId: options.accountId,
});
},
});
// Track last auto-generated
const lastAutoGeneratedSlug = useRef(slugify(props.name));
// Auto-generate on name change
useEffect(() => {
if (nameValue) {
const currentSlug = form.getValues('slug');
const generatedSlug = slugify(nameValue);
if (currentSlug === lastAutoGeneratedSlug.current) {
form.setValue('slug', generatedSlug);
slugValidation.validate(generatedSlug);
lastAutoGeneratedSlug.current = generatedSlug;
}
}
}, [nameValue]);
// Validate on slug change
useEffect(() => {
if (slugValue) {
slugValidation.validate(slugValue);
}
}, [slugValue]);
// Visual feedback in JSX
<Input {...field} className={ slugValidation.isUnique===false ? 'border-destructive' : slugValidation.isUnique===true
? 'border-green-500' : '' } />
{slugValidation.isValidating &&
<Loader2 className="animate-spin" />}
{slugValidation.isUnique === true &&
<CheckCircle />}
{slugValidation.isUnique === false &&
<AlertCircle />}
Database Patterns
RPC Function
CREATE OR REPLACE FUNCTION my_function( p_param1 uuid, p_param2 varchar ) RETURNS boolean LANGUAGE plpgsql SECURITY DEFINER SET search_path = '' AS $$ BEGIN -- Logic here RETURN true; END; $$; GRANT EXECUTE ON FUNCTION my_function TO authenticated;
RLS Policy
-- Enable RLS ALTER TABLE my_table ENABLE ROW LEVEL SECURITY; -- Read policy CREATE POLICY "Users can view their data" ON my_table FOR SELECT USING ( user_id = auth.uid() OR id IN (SELECT ... WHERE user_id = auth.uid()) ); -- Write policy CREATE POLICY "Users can update their data" ON my_table FOR UPDATE USING (user_id = auth.uid());
File Organization
feature/ ├── page.tsx # Route page ├── layout.tsx # Layout (optional) ├── loading.tsx # Loading state ├── error.tsx # Error boundary ├── _components/ # Feature components │ ├── feature-form.tsx │ └── feature-list.tsx └── _lib/ ├── server/ # Server-only code │ ├── loader.ts │ └── server-actions.ts ├── schemas/ # Zod schemas │ └── feature.schema.ts └── types.ts # TypeScript types
Component Patterns
Server Component
// Direct async/await
async function ServerComponent({ id }: Props) {
const data = await fetchData(id);
return <div>{data.name}</div>;
}
Client Component
'use client';
import { useState } from 'react';
export function ClientComponent({ data }: Props) {
const [state, setState] = useState(data);
// Event handlers, hooks, etc.
return <div onClick={handleClick}>{state}</div>;
}
Mixed Pattern
// Server Component (page)
async function Page() {
const data = await fetchData();
return
<ClientWrapper data={data} />;
}
// Client Component
'use client';
export function ClientWrapper({ data }: Props) {
// Client-side interactivity
}
Common Supabase Queries
Insert
const { data, error } = await client
.from('table')
.insert({ name: 'value' })
.select()
.single();
Update
const { error } = await client
.from('table')
.update({ name: 'new value' })
.eq('id', id);
Delete
const { error } = await client
.from('table')
.delete()
.eq('id', id);
Query with Relations
const { data } = await client
.from('projects')
.select(`
*,
account:accounts(name, slug),
members:projects_members(
user:auth.users(email)
)
`)
.eq('id', id)
.single();
RPC Call
const { data, error } = await client.rpc('function_name', {
p_param1: value1,
p_param2: value2,
});
Cache Management
Server-Side Revalidation
import { revalidatePath, revalidateTag } from 'next/cache';
// Revalidate specific path
revalidatePath('/home/team-slug');
// Revalidate layout
revalidatePath('/home/team-slug', 'layout');
// Revalidate by tag
revalidateTag('projects');
Client-Side Refresh
import { useRouter } from 'next/navigation';
const router = useRouter();
// Refresh current route
router.refresh();
// Navigate and refresh
router.push('/new-path');
Error Handling
Server Action
export const myAction = enhanceAction(
async (data) => {
try {
// Operation
return { success: true, data };
} catch (error) {
logger.error({ error }, 'Operation failed');
throw error;
}
},
{ schema: MySchema }
);
Client Component
try {
await serverAction(data);
toast.success('Success!');
} catch (error) {
if (!isRedirectError(error)) {
toast.error('Error occurred');
}
}
Redirect Patterns
Server Action
import { redirect } from 'next/navigation';
// After mutation
revalidatePath('/path');
redirect('/new-path');
Client Component
import { useRouter } from 'next/navigation';
const router = useRouter();
router.push('/new-path');
Translation Keys
// Component
<Trans i18nKey="teams:settings.pageTitle" />
// Hook
const { t } = useTranslation('teams');
const title = t('settings.pageTitle');
// Pluralization
<Trans i18nKey="projects:count" count={projects.length} />
Useful Imports
// UI Components
import { Button } from '@kit/ui/button';
import { Card, CardContent, CardHeader } from '@kit/ui/card';
import { Form, FormField, FormItem } from '@kit/ui/form';
import { Input } from '@kit/ui/input';
import { toast } from '@kit/ui/sonner';
import { Trans } from '@kit/ui/trans';
// React
import { useState, useEffect, useRef, useTransition } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
// Next.js
import { redirect } from 'next/navigation';
import { useRouter } from 'next/navigation';
import { revalidatePath } from 'next/cache';
// Supabase
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { useSupabase } from '@kit/supabase/hooks/use-supabase';
// Kit utilities
import { enhanceAction } from '@kit/next/actions';
import { getLogger } from '@kit/shared/logger';
import { withI18n } from '~/lib/i18n/with-i18n';
Testing
Run Tests
# All tests pnpm test # Specific package pnpm --filter web test # E2E tests pnpm --filter web-e2e test
Test Structure
import { describe, it, expect } from 'vitest';
describe('Feature', () => {
it('should work correctly', () => {
const result = myFunction();
expect(result).toBe(expected);
});
});
Debugging
Server Logs
import { getLogger } from '@kit/shared/logger';
const logger = await getLogger();
logger.info({ data }, 'Operation started');
logger.error({ error }, 'Operation failed');
Client Logs
console.log('Debug:', { data });
console.error('Error:', error);
Troubleshooting
TypeScript Errors
# Regenerate types pnpm supabase:web:typegen # Check all types pnpm typecheck
Database Issues
# Reset database pnpm supabase:web:reset # Check migrations pnpm --filter web supabase migrations list
Cache Issues
# Clear Next.js cache rm -rf .next # Clear turbo cache rm -rf .turbo