Skip to content

Application Conventions and Standards

This document outlines the standard patterns, structure, and conventions used across all Next.js applications in the homelab infrastructure.

Technology Stack

Standard Stack (All New Projects)

  • Next.js 16 - React framework with App Router
  • React 19 - Latest stable React
  • TypeScript 5+ - Strict mode enabled
  • Tailwind CSS v4 - Modern CSS-first approach with @theme inline syntax
  • Supabase - Backend as a service (PostgreSQL, Auth, Storage, Realtime)
  • lucide-react - Icon library

Package Versions

Maintain compatibility with these minimum versions:

{
  "dependencies": {
    "next": "^16.0.0",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "@supabase/supabase-js": "^2.78.0",
    "@supabase/ssr": "^0.7.0",
    "lucide-react": "^0.550.0"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4.0.0",
    "tailwindcss": "^4.0.0",
    "typescript": "^5.9.0"
  }
}

AI Integration Packages

For apps with AI features:

{
  "dependencies": {
    "@jakecelentano/ai-providers": "github:jakecelentano/ai-providers#v1.2.0",
    "@ai-sdk/anthropic": "^2.0.0",
    "@ai-sdk/react": "^2.0.0",
    "ai": "^5.0.0"
  }
}

Usage guidance: - ai-providers: Simple completions, summarization, non-streaming generation - @ai-sdk/* + ai: Streaming chat, tool-based agents, complex agentic behavior

See Code Patterns - AI Providers for detailed usage.

Next.js 16 Breaking Changes

Async Params (CRITICAL)

Next.js 16 introduced a breaking change where params in pages and API routes are now Promises that must be awaited.

Dynamic Route Pages

Page components (app/[param]/page.tsx):

// ❌ WRONG (Next.js 15 and earlier)
export default async function Page({ params }: { params: { id: string } }) {
  const { id } = params  // Will cause runtime error
  // ...
}

// ✅ CORRECT (Next.js 16+)
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
  const { id } = await params  // Must await the Promise
  // ...
}

API Routes

API route handlers (app/api/[param]/route.ts):

// ❌ WRONG
export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const { id } = params  // Will cause type error
  // ...
}

// ✅ CORRECT
export async function GET(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params  // Must await
  // ...
}

Common Errors

Type Error (at build time):

Type error: Type 'typeof import("./route")' does not satisfy the constraint 'RouteHandlerConfig'.
  Types of property 'GET' are incompatible.
    Property 'id' is missing in type 'Promise<{ id: string; }>'

Runtime Error (in browser):

Route '/[id]' used `params.id`. `params` is a Promise and must be unwrapped with `await`

Migration Checklist

When upgrading to Next.js 16 or creating new apps:

  1. ✅ Update all page component param types to Promise<{ ... }>
  2. ✅ Add await before destructuring params
  3. ✅ Update all API route handler param types
  4. ✅ Delete .next folder after making changes (rm -rf .next)
  5. ✅ Restart dev server to clear type cache

Note: This applies to ALL dynamic routes including: - app/[id]/page.tsx - app/api/[id]/route.ts - app/[slug]/[id]/page.tsx (nested params)

Directory Structure

Standard Layout

Projects should follow this structure (flat, no src/ directory):

app-name/
├── app/                           # Next.js App Router
│   ├── (dashboard)/              # Route group (optional)
│   ├── api/                      # API routes
│   ├── auth/                     # Auth pages
│   │   ├── callback/            # OAuth callback
│   │   └── login/               # Login page
│   ├── favicon.ico
│   ├── globals.css              # Global styles
│   ├── layout.tsx               # Root layout
│   └── page.tsx                 # Home page
├── components/                    # React components
│   ├── ui/                      # UI primitives (if using shadcn/ui)
│   └── [feature]/               # Feature-specific components
├── lib/                          # Libraries and utilities
│   ├── supabase/
│   │   ├── client.ts            # Browser Supabase client
│   │   ├── server.ts            # Server Supabase client
│   │   └── middleware.ts        # Session refresh helper
│   ├── utils/
│   │   ├── cn.ts                # Tailwind class merger
│   │   └── index.ts             # Utility exports
│   └── [feature]/               # Feature-specific utilities
├── types/                        # TypeScript definitions
│   └── database.ts              # Supabase generated types
├── supabase/                     # Supabase project files
│   ├── migrations/              # Database migrations
│   ├── config.toml              # Supabase configuration
│   └── seed.sql                 # Seed data (optional)
├── public/                       # Static assets
├── middleware.ts                 # Next.js middleware (auth)
├── .env.local                    # Environment variables (gitignored)
├── .env.local.example            # Environment template (committed)
├── .gitignore
├── components.json               # shadcn/ui config (optional)
├── eslint.config.mjs             # ESLint configuration
├── next.config.ts                # Next.js configuration
├── package.json
├── postcss.config.mjs            # Tailwind v4 PostCSS
├── README.md                     # Project documentation
└── tsconfig.json                 # TypeScript configuration

Directory Guidelines

app/ - Next.js routes - Use route groups (groupname) for layout without affecting URL - Keep API routes in api/ subdirectory - Auth-related pages in auth/

components/ - Reusable React components - Use ui/ subdirectory for shadcn/ui components - Organize by feature for complex apps (components/dashboard/, components/photos/) - Keep components focused and composable

lib/ - Business logic and utilities - lib/supabase/ - Always use this structure for Supabase clients - lib/utils/ - General utilities (formatters, helpers, etc.) - Feature-specific libs in lib/[feature]/

types/ - TypeScript type definitions - database.ts - Generated from Supabase schema - Other types as needed (index.ts, [feature].ts)

File Naming Conventions

  • Components: PascalCase - UserProfile.tsx, DashboardCard.tsx
  • Utilities: camelCase - formatDate.ts, apiClient.ts
  • Types: camelCase - database.ts, apiTypes.ts
  • Routes: lowercase with hyphens - app/user-settings/, app/api/get-data/
  • Config files: lowercase with dots - .env.local, next.config.ts

Supabase Integration

Complete Supabase integration patterns are documented in a separate reference:

→ See Supabase Integration Guide

This includes: - Client setup (browser, server, middleware) - Authentication patterns (login, middleware protection, RLS policies) - Database role grants (PostgREST access) - Multi-schema configuration (for production deployment) - Common issues and troubleshooting

Quick reference: - Browser client: lib/supabase/client.ts - Server client: lib/supabase/server.ts - Middleware: lib/supabase/middleware.ts - Always use separate clients for browser vs server contexts


TypeScript Configuration

tsconfig.json Standards

{
  "compilerOptions": {
    "target": "ES2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "strict": true,
    "noEmit": true,
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "incremental": true,
    "plugins": [{ "name": "next" }],
    "paths": {
      "@/*": ["./*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
  "exclude": ["node_modules"]
}

Type Generation

Generate Supabase types after schema changes:

npx supabase gen types typescript --local > types/database.ts

UI Components

shadcn/ui Integration

All projects should use shadcn/ui for standard UI components. This provides: - Accessible, tested components (WCAG AA compliant) - Consistent design patterns across apps - Tailwind CSS v4 compatible - Full TypeScript support - Copy-paste component code (no package dependencies)

Setup

Initialize shadcn/ui in your project:

npx shadcn@latest init

Follow the prompts: - Style: Default - Base color: Blue (matches our design system) - CSS variables: Yes - Import alias: @/* (default)

This creates components.json configuration.

Adding Components

Use the shadcn CLI to add components:

# Add single component
npx shadcn@latest add button

# Add multiple components
npx shadcn@latest add button card dialog form input

Components are copied to components/ui/ - you own the code and can customize freely.

Available Components (449+ total)

Core UI (60+): - Forms: button, input, textarea, select, checkbox, radio-group, switch - Feedback: alert, toast (sonner), dialog, alert-dialog, drawer - Data: table, card, badge, avatar, skeleton - Navigation: tabs, breadcrumb, dropdown-menu, navigation-menu - Layout: separator, scroll-area, resizable, sidebar

Blocks (90+): - Complete page sections: sidebars, login forms, dashboards, calendars - Copy-paste ready implementations

Charts (100+): - Built on Recharts: area, bar, line, pie, radar, radial charts - Interactive, accessible, responsive

See all components: Ask Claude "show me shadcn components" (requires MCP setup)

When to Use shadcn vs Custom

Use shadcn when: - Component exists in registry (button, form, table, etc.) - Need accessible, tested implementation - Want consistency across apps - Building standard UI patterns

Build custom when: - Highly specialized behavior not in registry - Simple enough that shadcn adds complexity - Performance-critical (shadcn has overhead) - Design significantly differs from shadcn defaults

Component Customization

shadcn components are fully customizable - they're just TypeScript files in your codebase:

// components/ui/button.tsx - Customize as needed
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'default' | 'destructive' | 'outline' | 'ghost' | 'link'
  size?: 'default' | 'sm' | 'lg' | 'icon'
  // Add your own variants:
  loading?: boolean
}

export function Button({ loading, children, ...props }: ButtonProps) {
  return (
    <button disabled={loading} {...props}>
      {loading ? <Spinner /> : children}
    </button>
  )
}

Styling: Modify Tailwind classes directly in the component file.

MCP Integration

Enable Claude to search and suggest components:

npx shadcn@latest mcp init --client claude

Then ask Claude: - "Show me all shadcn form components" - "How do I use the data table component?" - "Find a calendar component with time picker"

See /root/tower-fleet/docs/reference/mcp-tools.md for details.

Component Composition Patterns

Compose shadcn components for complex UIs:

import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'

export function UserProfileCard() {
  return (
    <Card>
      <CardHeader>
        <CardTitle>User Profile</CardTitle>
      </CardHeader>
      <CardContent>
        <form className="space-y-4">
          <Input placeholder="Name" />
          <Input type="email" placeholder="Email" />
          <Button type="submit">Save Changes</Button>
        </form>
      </CardContent>
    </Card>
  )
}

Best practices: - Compose shadcn primitives into feature components - Keep shadcn components in components/ui/ unchanged - Build feature components in components/[feature]/ - Avoid deep prop drilling - use composition

Accessibility

All shadcn components follow WCAG AA standards: - Proper ARIA attributes - Keyboard navigation - Focus management - Screen reader support

Additional accessibility considerations: - Verify color contrast for custom variants (use Chrome DevTools) - Test keyboard navigation after customization - Provide alt text for icons used as buttons - Use semantic HTML (<button> not <div onClick>)

See /root/tower-fleet/docs/reference/styling-guide.md for complete accessibility guidelines.

Styling Conventions

Tailwind CSS v4

postcss.config.mjs:

export default {
  plugins: {
    '@tailwindcss/postcss': {}
  }
}

globals.css:

@import "tailwindcss";

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  /* Custom theme variables */
}

Class Name Management

Always use the cn utility for conditional classes:

import { cn } from '@/lib/utils'

export function Button({ className, variant = 'primary', ...props }) {
  return (
    <button
      className={cn(
        'px-4 py-2 rounded font-medium',
        variant === 'primary' && 'bg-blue-500 text-white',
        variant === 'secondary' && 'bg-gray-200 text-gray-900',
        className
      )}
      {...props}
    />
  )
}

Form Input Styling

Standard form input pattern with proper contrast:

<input
  type="text"
  className="w-full border border-gray-300 rounded-lg px-3 py-2 text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
  placeholder="Enter value..."
/>

Key classes for inputs: - text-gray-900 - Required: Dark text for visibility - placeholder:text-gray-400 - Required: Lighter placeholder text - border-gray-300 - Standard border color - focus:ring-2 focus:ring-blue-500 - Focus state indicator

Textarea pattern:

<textarea
  rows={3}
  className="w-full border border-gray-300 rounded-lg px-3 py-2 text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
  placeholder="Enter description..."
/>

Select dropdown pattern:

<select className="w-full border border-gray-300 rounded-lg px-3 py-2 text-gray-900 focus:outline-none focus:ring-2 focus:ring-blue-500">
  <option value="">Select option...</option>
  <option value="1">Option 1</option>
</select>

Label pattern:

<label htmlFor="field-id" className="block text-sm font-medium text-gray-700 mb-1">
  Field Label
</label>

Error message pattern:

{error && (
  <div className="bg-red-50 border border-red-200 rounded-lg p-3">
    <p className="text-red-800 text-sm">{error}</p>
  </div>
)}

Complete form example:

<form onSubmit={handleSubmit} className="space-y-4">
  <div>
    <label htmlFor="title" className="block text-sm font-medium text-gray-700 mb-1">
      Title *
    </label>
    <input
      type="text"
      id="title"
      name="title"
      required
      className="w-full border border-gray-300 rounded-lg px-3 py-2 text-gray-900 placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
      placeholder="e.g., My Project"
    />
  </div>

  <button
    type="submit"
    disabled={isLoading}
    className="w-full bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 disabled:opacity-50"
  >
    {isLoading ? 'Submitting...' : 'Submit'}
  </button>
</form>

Common mistake to avoid:

// ❌ WRONG: No text color - text will be nearly invisible
<input className="w-full border border-gray-300 rounded-lg px-3 py-2" />

// ✅ CORRECT: Dark text with visible placeholder
<input className="w-full border border-gray-300 rounded-lg px-3 py-2 text-gray-900 placeholder:text-gray-400" />


Code Patterns

Complete code patterns for API routes, AI SDK integration, error handling, and React patterns are documented in a separate reference:

→ See Code Patterns

This includes: - Import organization - Async component patterns - API route examples (including AI SDK v5 chat routes) - Tool use patterns with Zod schemas - Error handling - Server Actions - Form handling - Edge runtime considerations


Development Workflows

Local Development

Standard dev server command:

npm run dev -- -H 0.0.0.0

The -H 0.0.0.0 flag makes the server accessible from outside the container.

Tmux Sessions

Always run dev servers in named tmux sessions:

# Create session
tmux new -s app-name

# Inside tmux
npm run dev -- -H 0.0.0.0

# Detach: Ctrl+B, then D
# Reattach: tmux attach -t app-name

Database Migrations

Create migration:

npx supabase migration new migration_name

Apply migrations:

npx supabase db push

Reset database:

npx supabase db reset


Docker Deployment

Complete Docker deployment guide including Dockerfile structure, build args, multi-schema configuration, and common issues is documented separately:

→ See Docker Deployment

This includes: - Next.js standalone configuration - Multi-stage Dockerfile template - Build-time environment variables (critical for Supabase) - CMD syntax requirements - .dockerignore patterns - Multi-schema Supabase client configuration - Insecure registry setup - Common Docker issues and solutions


Ingress Configuration

Complete Ingress configuration patterns for exposing applications in Kubernetes are documented separately:

→ See Ingress Configuration

This includes: - When to use Ingress vs LoadBalancer - Standard Ingress manifest patterns - Service configuration (ClusterIP) - DNS configuration (OPNsense) - Common annotations - Migration from LoadBalancer to Ingress


Package.json Scripts

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint",
    "type-check": "tsc --noEmit",
    "db:types": "npx supabase gen types typescript --local > types/database.ts",
    "db:push": "npx supabase db push",
    "db:reset": "npx supabase db reset"
  }
}

Git Configuration

.gitignore Essentials

# Next.js
/.next/
/out/

# Env files
.env*
!.env.local.example

# Supabase
supabase/.branches
supabase/.temp
.supabase

# Node
node_modules/

Commit Message Format

Follow conventional commits:

Single-line (preferred):

type: brief summary

Multi-line (when needed):

type: brief summary

Additional context or details

Types: feat, fix, docs, refactor, chore, style, test

Documentation Requirements

README.md

Every project must have a README with:

  1. Description - What the app does
  2. Stack - Tech stack used
  3. Development Setup - Complete setup instructions
  4. Common Commands - npm scripts, Supabase commands
  5. Project Structure - Directory layout
  6. Links - Related documentation

.env.local.example

Always include an example environment file with:

  • All required variables
  • Placeholder values
  • Comments explaining each variable
  • Instructions for obtaining values

Additional Resources

Workflow Guides: - Creating a New App - Step-by-step guide for new projects - Development Environment - Local and K8s Supabase setup - Docker Deployment - Building and deploying Docker containers - Database Migrations - Managing schema changes - Production Deployment - Kubernetes deployment workflow

Reference Docs: - Supabase Integration - Complete Supabase patterns and auth setup - Code Patterns - API routes, AI SDK, error handling - Ingress Configuration - Kubernetes Ingress patterns - Supabase Multi-App Architecture - Schema isolation patterns - Styling Guide - Accessibility and design standards - MCP Tools - AI assistant integrations (shadcn, etc.) - Troubleshooting - Common issues and solutions

Deviations from Standards

When deviating from these conventions:

  1. Document the reason - Add comment or README section
  2. Consider if it should be standard - Update this doc if beneficial
  3. Be consistent within the project - Don't mix patterns

Example acceptable deviations:

  • Using src/ directory for complex apps
  • Additional UI libraries (e.g., Recharts for charts)
  • Custom build configurations for Docker deployment