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
@themeinline 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):
Migration Checklist¶
When upgrading to Next.js 16 or creating new apps:
- ✅ Update all page component param types to
Promise<{ ... }> - ✅ Add
awaitbefore destructuring params - ✅ Update all API route handler param types
- ✅ Delete
.nextfolder after making changes (rm -rf .next) - ✅ 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:
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:
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:
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:
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:
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:
Apply migrations:
Reset database:
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¶
Recommended 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):
Multi-line (when needed):
Types: feat, fix, docs, refactor, chore, style, test
Documentation Requirements¶
README.md¶
Every project must have a README with:
- Description - What the app does
- Stack - Tech stack used
- Development Setup - Complete setup instructions
- Common Commands - npm scripts, Supabase commands
- Project Structure - Directory layout
- 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:
- Document the reason - Add comment or README section
- Consider if it should be standard - Update this doc if beneficial
- 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