Skip to content
Back to skills

1228 Patterns 78664dc9

ASecurity

Consistent patterns across the codebase make code easier for future developers (and Claude) to navigate.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
developmenttypescriptreactnextjsnodetestingdatabase

Works with

  • cli

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 1228-patterns_78664dc9 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 1228 Patterns 78664dc9?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 1228 Patterns 78664dc9
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-1228-patterns-78664dc9/badge)](https://www.skillsdirectory.com/skills/tools-only-1228-patterns-78664dc9)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
---
paths:
  - "**/*.ts"
  - "**/*.tsx"
---

# Patterns — Next.js Supabase TypeScript

Consistent patterns across the codebase make code easier for future developers (and Claude) to navigate.

## Data Fetching: Server Components with Loaders

Async server components call loader functions for initial data:

```typescript
// Page: app/home/[account]/projects/page.tsx
async function ProjectsPage({ params }: Props) {
  const { account } = await params;
  const supabase = await createClient();
  const projects = await loadProjectsPageData(supabase, account);
  return <ProjectsList projects={projects} />;
}
```

```typescript
// Loader: _lib/server/projects-page.loader.ts
import 'server-only';

import { SupabaseClient } from '@supabase/supabase-js';
import { Database } from '@/types/database';

export async function loadProjectsPageData(
  client: SupabaseClient<Database>,
  slug: string,
) {
  const { data, error } = await client
    .from('projects')
    .select('*')
    .eq('account_slug', slug);
  if (error) throw error;
  return data ?? [];
}
```

## Mutations: Server Actions with Validation

Every Server Action must validate inputs and verify authentication:

```typescript
// _lib/server/server-actions.ts
'use server';

import { z } from 'zod';
import { createClient } from '@/lib/supabase/server';
import { getSession } from '@/lib/auth';
import { createProjectsService } from './projects.service';
import { CreateProjectSchema } from '../schema/create-project.schema';

export async function createProjectAction(formData: FormData) {
  const session = await getSession();
  if (!session) throw new Error('Unauthorized');

  const data = CreateProjectSchema.parse(Object.fromEntries(formData));
  const supabase = await createClient();
  const service = createProjectsService(supabase);
  return service.createProject(data);
}
```

<!-- CUSTOMIZE: If your framework provides a Server Action wrapper that handles auth + validation,
use it consistently instead of manual checks. -->

## Service Pattern

Private class + factory function enables dependency injection for testing while keeping class internals hidden:

```typescript
import 'server-only';

import { SupabaseClient } from '@supabase/supabase-js';
import { Database } from '@/types/database';

class ProjectsService {
  constructor(private client: SupabaseClient<Database>) {}

  async createProject(data: CreateProjectData) {
    const { data: result, error } = await this.client
      .from('projects')
      .insert(data)
      .select()
      .single();
    if (error) throw error;
    return result;
  }
}

export function createProjectsService(client: SupabaseClient<Database>) {
  return new ProjectsService(client);
}
```

## Supabase Client Selection

<!-- CUSTOMIZE: Update import paths to match your Supabase client setup.
If using @supabase/ssr, typical pattern is createClient() for server and createBrowserClient() for client. -->

| Context | Client | Import |
|---------|--------|--------|
| Server Components, Actions, Route Handlers | `createClient()` | `@/lib/supabase/server` |
| Client Components | `createBrowserClient()` or `useSupabase()` | `@/lib/supabase/client` |
| Bypassing RLS (rare) | Admin client | `@/lib/supabase/admin` |

## Route Structure

```
app/home/[account]/
├── feature/
│   ├── page.tsx                    # Server Component page
│   ├── _components/                # Feature-specific UI
│   │   └── feature-list.tsx
│   └── _lib/
│       ├── server/                 # Server-side logic
│       │   ├── feature-page.loader.ts
│       │   └── server-actions.ts
│       └── schema/                 # Zod validation (singular)
│           └── feature.schema.ts
```

## Multi-Tenant Account Model

<!-- CUSTOMIZE: Adjust to match your auth/account model. Remove if not multi-tenant. -->

- **Personal Accounts:** User's own workspace
- **Team Accounts:** Shared workspaces with members via memberships table
- **Data isolation:** All data links to accounts via `account_id` foreign key
- **RLS enforces access:** No manual auth checks needed with standard Supabase client

## Client-Side Data Fetching: React Query

When fetching data in Client Components, use `useQuery` from `@tanstack/react-query`. Separate the data provider from the presenter:

```typescript
'use client';

import { useQuery } from '@tanstack/react-query';
import { createBrowserClient } from '@/lib/supabase/client';

// Data provider component
function RolesDataProvider({ children }: { children: (data: Role[]) => React.ReactNode }) {
  const client = createBrowserClient();
  const { data, isLoading, error } = useQuery({
    queryKey: ['roles'],
    queryFn: () => client.from('roles').select('*').then(({ data }) => data ?? []),
  });

  if (isLoading) return <LoadingSpinner />;
  if (error) return <ErrorMessage error={error} />;
  return children(data);
}

// Presenter component
function RolesList({ roles }: { roles: Role[] }) {
  return <ul>{roles.map(r => <li key={r.id}>{r.name}</li>)}</ul>;
}
```

## Redirect After Server Action

Use `redirect()` server-side. Handle in client with `isRedirectError`:

```typescript
import { isRedirectError } from 'next/dist/client/components/redirect-error';

try {
  await myServerAction(data);
} catch (error) {
  if (isRedirectError(error)) throw error;
  // handle real error
}
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.