Skip to main content
Glama
architect.yaml25.7 kB
features: - name: nextjs-page-pattern design_pattern: Next.js Page Component Pattern includes: - src/app/**/page.tsx description: |- ## Pattern Overview Design pattern for Next.js 15 App Router page components using Server Components by default with proper metadata, data fetching, and error handling. ## What TO DO ✅ - Export async Server Components by default for data fetching - Export metadata or generateMetadata() for SEO - Use proper TypeScript types for params and searchParams - Fetch data at the component level using async/await - Use Suspense boundaries for loading states - Handle errors with error.tsx boundary files - Keep components simple and delegate UI to separate components - Use absolute imports with @/ alias - Follow proper naming: page.tsx exports default function Page() ## What NOT TO DO ❌ - Don't add 'use client' unless absolutely necessary (interactivity needed) - Don't fetch data in useEffect hooks in Server Components - Don't mix server and client logic in the same component - Don't forget to export metadata for SEO - Don't use useState/useEffect in Server Components - Don't access searchParams synchronously (must be awaited in Next.js 15) - Don't put complex UI logic directly in page.tsx ## Examples ### Basic Page with Data Fetching ```typescript import type { Metadata } from 'next'; import { db } from '@/db/drizzle'; import { users } from '@/db/schema'; export const metadata: Metadata = { title: 'Users', description: 'User list page', }; export default async function UsersPage() { const userList = await db.select().from(users); return ( <main> <h1>Users</h1> <UserList users={userList} /> </main> ); } ``` ### Dynamic Page with Params ```typescript interface PageProps { params: Promise<{ id: string }>; } export async function generateMetadata({ params }: PageProps) { const { id } = await params; return { title: `Post: ${id}` }; } export default async function PostPage({ params }: PageProps) { const { id } = await params; return <article>{/* content */}</article>; } ``` - name: nextjs-layout-pattern design_pattern: Next.js Layout Component Pattern includes: - src/app/**/layout.tsx description: |- ## Pattern Overview Design pattern for Next.js 15 App Router layout components that wrap child pages with shared UI, metadata, and behavior. ## What TO DO ✅ - Accept children prop and render it in the layout - Export metadata at root layout (app/layout.tsx) for site-wide defaults - Use Server Components by default - Add route group protection logic (auth checks) at layout level - Keep layouts focused on structural UI (navigation, sidebars, headers) - Use proper TypeScript types for props - Apply global styles in root layout only - Define font variables in root layout - Nest layouts for progressive enhancement ## What NOT TO DO ❌ - Don't forget to render {children} prop - Don't add 'use client' unless client-side state is required - Don't fetch the same data in both layout and page (hoist to layout) - Don't apply global styles in nested layouts - Don't create unnecessary layout nesting - Don't forget route group redirects for protected routes - Don't use layouts for one-off page wrapping (use page composition instead) ## Examples ### Root Layout ```typescript import type { Metadata } from 'next'; import { Inter } from 'next/font/google'; import '@/app/globals.css'; const inter = Inter({ subsets: ['latin'] }); export const metadata: Metadata = { title: { default: 'My App', template: '%s | My App' }, description: 'App description', }; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body className={inter.className}>{children}</body> </html> ); } ``` ### Protected Layout with Auth ```typescript import { auth } from '@/lib/auth'; import { redirect } from 'next/navigation'; import { headers } from 'next/headers'; export default async function ProtectedLayout({ children }: { children: React.ReactNode }) { const session = await auth.api.getSession({ headers: await headers() }); if (!session) redirect('/sign-in'); return ( <div> <nav>{/* Nav */}</nav> <main>{children}</main> </div> ); } ``` - name: nextjs-api-route-pattern design_pattern: Next.js API Route Handler Pattern includes: - src/app/api/**/route.ts description: |- ## Pattern Overview Design pattern for Next.js 15 App Router API routes using Web Request/Response API with proper validation, error handling, and service delegation. ## What TO DO ✅ - Export async functions named after HTTP methods (GET, POST, PUT, DELETE, PATCH) - Use NextResponse.json() for JSON responses with proper status codes - Validate input with Zod schemas before processing - Delegate business logic to services (never access db directly) - Handle errors with try-catch and return appropriate status codes - Add authentication/authorization checks when needed - Use proper TypeScript types for request/response - Return consistent response format { success, data?, error? } - Add CORS headers for public APIs if needed - Use revalidatePath/revalidateTag for cache invalidation ## What NOT TO DO ❌ - Don't put business logic directly in route handlers - Don't access database directly (use services instead) - Don't forget input validation - Don't return raw errors to client (sanitize error messages) - Don't forget authentication checks for protected routes - Don't use res/req from Next.js 12 (use Request/Response) - Don't mix route.ts with page.ts in same segment - Don't forget to handle different HTTP methods separately ## Examples ### POST Route with Validation ```typescript import { NextResponse } from 'next/server'; import { z } from 'zod'; import { UserService } from '@/services/UserService'; const schema = z.object({ email: z.string().email(), name: z.string().min(2), }); export async function POST(request: Request) { try { const body = await request.json(); const data = schema.parse(body); const user = await new UserService().create(data); return NextResponse.json({ success: true, data: user }, { status: 201 }); } catch (error) { if (error instanceof z.ZodError) { return NextResponse.json({ success: false, error: error.errors }, { status: 400 }); } return NextResponse.json({ success: false, error: 'Internal error' }, { status: 500 }); } } ``` ### Protected Route with Auth ```typescript import { NextResponse } from 'next/server'; import { auth } from '@/lib/auth'; import { headers } from 'next/headers'; export async function GET() { const session = await auth.api.getSession({ headers: await headers() }); if (!session) { return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 }); } return NextResponse.json({ success: true, data: { user: session.user } }); } ``` - name: react-component-pattern design_pattern: Smart/Dumb Component Pattern with Tailwind CSS includes: - src/components/**/*.tsx - src/app/**/_ui/components/**/*.tsx description: |- ## Pattern Overview Design pattern for React components using the Smart/Dumb (Container/Presentational) pattern with TypeScript, Tailwind CSS, and Storybook integration. Components are split into smart components (logic) and dumb components (UI). ## Component Structure Each component directory contains three files: - `index.tsx` - Smart component (handles state, logic, data fetching) - `{ComponentName}View.tsx` - Dumb component (pure UI, props-driven) - `{ComponentName}.stories.tsx` - Stories (render the View component) ## What TO DO ✅ ### Smart Component (index.tsx) - Import and delegate rendering to the View component - Handle business logic, state management, and data fetching - Pass data and callbacks as props to the View - Export as default for simpler imports - Add 'use client' directive when client interactivity is needed ### Dumb Component ({Name}View.tsx) - Export props type for use in stories and smart component - Keep as pure functions of their props (no hooks, no state) - Use named export (not default) - Use cn() utility from @/lib/utils for className merging - Extend React.ComponentPropsWithoutRef for prop inheritance ### Stories ({Name}.stories.tsx) - Import and render the View component, NOT the smart component - Include Playground story (REQUIRED) showing all variants - Include state stories (SUGGESTED): Default, Loading, Error, Empty, Disabled, etc. - Use args and argTypes for interactive controls ## What NOT TO DO ❌ - Don't put UI rendering logic in smart components - Don't use hooks or state in View components - Don't fetch data in View components - Don't import the smart component in stories - Don't use default export for View components - Don't skip the Playground story - Don't mix server and client component logic ## Examples ### Smart Component (index.tsx) ```typescript 'use client'; import * as React from 'react'; import { ButtonView, type ButtonViewProps } from './ButtonView'; type ButtonProps = ButtonViewProps & { onAsyncClick?: () => Promise<void>; }; export default function Button({ onAsyncClick, onClick, ...props }: ButtonProps) { const [loading, setLoading] = React.useState(false); const handleClick = async (e: React.MouseEvent<HTMLButtonElement>) => { if (onAsyncClick) { setLoading(true); await onAsyncClick(); setLoading(false); } onClick?.(e); }; return <ButtonView {...props} loading={loading} onClick={handleClick} />; } ``` ### Dumb Component (ButtonView.tsx) ```typescript import { cn } from '@/lib/utils'; export type ButtonViewProps = { variant?: 'primary' | 'secondary'; loading?: boolean; } & React.ComponentPropsWithoutRef<'button'>; export function ButtonView({ variant = 'primary', loading, className, children, disabled, ...rest }: ButtonViewProps) { return ( <button className={cn( 'rounded px-4 py-2 font-medium transition-colors', variant === 'primary' && 'bg-blue-600 text-white hover:bg-blue-700', variant === 'secondary' && 'bg-gray-200 text-gray-900 hover:bg-gray-300', className )} disabled={disabled || loading} {...rest} > {loading ? 'Loading...' : children} </button> ); } ``` ### Stories (Button.stories.tsx) ```typescript import type { Meta, StoryObj } from '@storybook/react'; import { ButtonView } from './ButtonView'; const meta = { title: 'Components/Button', component: ButtonView, parameters: { layout: 'centered' }, tags: ['autodocs'], } satisfies Meta<typeof ButtonView>; export default meta; type Story = StoryObj<typeof meta>; // REQUIRED: Playground showing all variants export const Playground: Story = { render: () => ( <div className="flex flex-col gap-4"> <ButtonView variant="primary">Primary</ButtonView> <ButtonView variant="secondary">Secondary</ButtonView> <ButtonView loading>Loading</ButtonView> <ButtonView disabled>Disabled</ButtonView> </div> ), }; // SUGGESTED: Individual state stories export const Default: Story = { args: { children: 'Click me' }, }; export const Loading: Story = { args: { loading: true, children: 'Loading...' }, }; export const Disabled: Story = { args: { disabled: true, children: 'Disabled' }, }; ``` - name: server-action-pattern design_pattern: Next.js Server Action Pattern includes: - src/actions/**/*.ts - src/app/**/_ui/actions/**/*.ts description: |- ## Pattern Overview Design pattern for Next.js server actions with 'use server' directive, Zod validation, service delegation, and proper error handling. ## What TO DO ✅ - Add 'use server' directive at the top of the file - Export async functions that return result objects - Validate input with Zod schemas before processing - Delegate business logic to services (NEVER access db directly) - Return consistent format: { success: boolean, data?: T, error?: string } - Use revalidatePath() or revalidateTag() for cache invalidation - Add authentication/authorization checks when needed - Handle errors with try-catch blocks - Use TypeScript types for inputs and outputs - Keep actions thin - validate, call service, revalidate cache ## What NOT TO DO ❌ - Don't access database directly in actions (use services) - Don't put complex business logic in actions - Don't forget input validation - Don't return raw error objects to client - Don't skip cache revalidation after mutations - Don't forget 'use server' directive - Don't expose sensitive data in return values - Don't use actions for data fetching (use Server Components instead) - Don't trust client-provided data without validation ## Examples ### Basic Server Action ```typescript 'use server'; import { z } from 'zod'; import { revalidatePath } from 'next/cache'; import { UserService } from '@/services/UserService'; const schema = z.object({ email: z.string().email(), name: z.string().min(2), }); export async function createUser(input: z.infer<typeof schema>) { try { const data = schema.parse(input); const user = await new UserService().create(data); revalidatePath('/users'); return { success: true, data: user }; } catch (error) { return { success: false, error: error instanceof z.ZodError ? 'Invalid input' : 'Failed to create user' }; } } ``` ### Protected Server Action ```typescript 'use server'; import { auth } from '@/lib/auth'; import { headers } from 'next/headers'; import { revalidatePath } from 'next/cache'; import { ProductService } from '@/services/ProductService'; export async function updateProduct(id: string, data: { name: string; price: number }) { const session = await auth.api.getSession({ headers: await headers() }); if (!session) return { success: false, error: 'Unauthorized' }; const product = await new ProductService().update(id, data); revalidatePath('/products'); return { success: true, data: product }; } ``` - name: service-layer-pattern design_pattern: Service Layer Pattern with Drizzle ORM includes: - src/services/**/*.ts description: |- ## Pattern Overview Design pattern for service classes that encapsulate business logic and database operations using Drizzle ORM with proper separation of concerns and dependency injection. ## What TO DO ✅ - Create class-based services with clear single responsibility - Import db from '@/db/drizzle' for database access - Define service interfaces in src/services/types.ts - Use async/await for all database operations - Return typed results from service methods - Handle errors with try-catch and return error objects - Use Drizzle query builder for type-safe queries - Name methods with clear verbs (getById, create, update, delete, findBy...) - Keep services focused on single entity or domain - Use dependency injection for composing services - Export both interface and implementation ## What NOT TO DO ❌ - Don't create static-only utility classes (use functions instead) - Don't mix multiple unrelated concerns in one service - Don't forget error handling - Don't expose Drizzle internals to callers - Don't use synchronous operations for database access - Don't create services without interfaces - Don't put UI logic or validation in services - Don't forget to export service interfaces from types.ts ## Examples ### Basic Service with CRUD ```typescript import { db } from '@/db/drizzle'; import { users } from '@/db/schema'; import { eq } from 'drizzle-orm'; export class UserService { async getById(id: string) { const [user] = await db.select().from(users).where(eq(users.id, id)).limit(1); return user || null; } async getAll() { return await db.select().from(users); } async create(data: { email: string; name: string }) { const [user] = await db.insert(users).values(data).returning(); return user; } async update(id: string, data: Partial<{ email: string; name: string }>) { const [user] = await db.update(users).set(data).where(eq(users.id, id)).returning(); return user || null; } } ``` ### Service with Complex Query ```typescript import { db } from '@/db/drizzle'; import { products, categories } from '@/db/schema'; import { eq, and, gte, lte, desc } from 'drizzle-orm'; export class ProductService { async getById(id: string) { const [result] = await db .select() .from(products) .leftJoin(categories, eq(products.categoryId, categories.id)) .where(eq(products.id, id)) .limit(1); return result || null; } async findByPriceRange(min: number, max: number) { return await db .select() .from(products) .where(and(gte(products.price, min), lte(products.price, max))) .orderBy(desc(products.price)); } } ``` - name: drizzle-schema-pattern design_pattern: Drizzle ORM Database Schema Pattern includes: - src/db/**/*.ts - drizzle.config.ts description: |- ## Pattern Overview Design pattern for defining database schemas using Drizzle ORM with PostgreSQL, including proper column types, constraints, relationships, and indexing. ## What TO DO ✅ - Import table builder from 'drizzle-orm/pg-core' - Use pgTable() to define tables with clear, descriptive names - Add timestamps (createdAt, updatedAt) to all tables - Use UUID or serial for primary keys - Define foreign key relationships with references() - Add proper indexes for frequently queried columns - Use TypeScript types for schema inference - Export schema types using typeof and InferSelectModel/InferInsertModel - Keep auth-related tables in separate schema file (auth-schema.ts) - Use descriptive column names in camelCase - Add default values where appropriate - Use correct PostgreSQL column types (uuid, timestamp, text, varchar, integer, boolean) ## What NOT TO DO ❌ - Don't forget to export schemas from main schema.ts - Don't skip timestamps on tables - Don't use generic names like 'data' or 'info' - Don't forget to add indexes for foreign keys - Don't mix auth schema with application schema - Don't use incorrect column types (use varchar with length, not unlimited text for names) - Don't forget to run migrations after schema changes - Don't create circular dependencies between tables ## Examples ### Basic Table Schema ```typescript import { pgTable, timestamp, uuid, varchar } from 'drizzle-orm/pg-core'; export const users = pgTable('users', { id: uuid('id').defaultRandom().primaryKey(), email: varchar('email', { length: 255 }).notNull().unique(), name: varchar('name', { length: 255 }).notNull(), createdAt: timestamp('created_at').defaultNow().notNull(), updatedAt: timestamp('updated_at').defaultNow().notNull(), }); export type User = typeof users.$inferSelect; export type InsertUser = typeof users.$inferInsert; ``` ### Table with Foreign Key & Index ```typescript import { pgTable, text, timestamp, uuid, varchar, index } from 'drizzle-orm/pg-core'; import { users } from './schema'; export const posts = pgTable( 'posts', { id: uuid('id').defaultRandom().primaryKey(), title: varchar('title', { length: 255 }).notNull(), content: text('content').notNull(), userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), createdAt: timestamp('created_at').defaultNow().notNull(), updatedAt: timestamp('updated_at').defaultNow().notNull(), }, (table) => ({ userIdx: index('user_idx').on(table.userId) }) ); export type Post = typeof posts.$inferSelect; ``` - name: better-auth-pattern design_pattern: Better Auth Configuration Pattern includes: - src/lib/auth.ts - src/lib/auth-client.ts - src/db/auth-schema.ts - middleware.ts description: |- ## Pattern Overview Design pattern for implementing Better Auth authentication with Drizzle adapter, proper server/client separation, and route protection patterns. ## What TO DO ✅ - Separate server auth (auth.ts) from client auth (auth-client.ts) - Use Drizzle adapter with PostgreSQL - Configure auth providers in server auth only - Export typed client hooks from auth-client.ts - Add auth schema to separate file (auth-schema.ts) - Export auth tables from main schema.ts file - Use middleware.ts for route protection - Add session checks in protected layouts - Use headers() from next/headers for server-side session access - Configure BETTER_AUTH_SECRET and BETTER_AUTH_URL in environment - Add provider credentials for OAuth (GOOGLE_CLIENT_ID, etc.) ## What NOT TO DO ❌ - Don't configure auth providers in client file - Don't access auth directly from client components (use hooks) - Don't forget to export auth schema from main schema - Don't skip database migration after adding auth schema - Don't hardcode secrets in code - Don't forget to protect routes with middleware or layout checks - Don't mix server and client auth code - Don't expose auth secrets to client ## Examples ### Server Auth (auth.ts) ```typescript import { betterAuth } from 'better-auth'; import { drizzleAdapter } from 'better-auth/adapters/drizzle'; import { db } from '@/db/drizzle'; import * as schema from '@/db/schema'; export const auth = betterAuth({ database: drizzleAdapter(db, { provider: 'pg', schema }), emailAndPassword: { enabled: true }, socialProviders: { google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }, }, secret: process.env.BETTER_AUTH_SECRET!, baseURL: process.env.BETTER_AUTH_URL!, }); ``` ### Client Auth (auth-client.ts) ```typescript 'use client'; import { createAuthClient } from 'better-auth/react'; export const authClient = createAuthClient({ baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL!, }); export const { useSession, signIn, signOut } = authClient; ``` ### Protected Layout ```typescript import { auth } from '@/lib/auth'; import { redirect } from 'next/navigation'; import { headers } from 'next/headers'; export default async function ProtectedLayout({ children }: { children: React.ReactNode }) { const session = await auth.api.getSession({ headers: await headers() }); if (!session) redirect('/sign-in'); return ( <div> <nav><p>Welcome, {session.user.name}</p></nav> <main>{children}</main> </div> ); } ```

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/AgiFlow/aicode-toolkit'

If you have feedback or need assistance with the MCP directory API, please join our Discord server