diff --git a/LOVABLE_CLONE_ADVANCED_FEATURES.md b/LOVABLE_CLONE_ADVANCED_FEATURES.md new file mode 100644 index 00000000..a7e6d04c --- /dev/null +++ b/LOVABLE_CLONE_ADVANCED_FEATURES.md @@ -0,0 +1,815 @@ +# πŸš€ Lovable Clone - Advanced Features & Production Setup + +> Edge Functions, Webhooks, Testing, CI/CD, Monitoring, vΓ  Advanced Features + +--- + +# πŸ“‘ Table of Contents + +1. [Supabase Edge Functions](#supabase-edge-functions) +2. [Webhook Handlers](#webhook-handlers) +3. [Testing Setup](#testing-setup) +4. [CI/CD Pipeline](#cicd-pipeline) +5. [Monitoring & Analytics](#monitoring--analytics) +6. [Advanced Features](#advanced-features) +7. [Production Deployment](#production-deployment) + +--- + +# ⚑ I. SUPABASE EDGE FUNCTIONS + +## 1. AI Chat Edge Function + +**File: `supabase/functions/chat-completion/index.ts`** + +```typescript +import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.39.0'; +import OpenAI from 'https://esm.sh/openai@4.28.0'; + +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type' +}; + +serve(async (req) => { + // Handle CORS + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }); + } + + try { + // Create Supabase client + const supabaseClient = createClient( + Deno.env.get('SUPABASE_URL') ?? '', + Deno.env.get('SUPABASE_ANON_KEY') ?? '', + { + global: { + headers: { Authorization: req.headers.get('Authorization')! } + } + } + ); + + // Verify user + const { + data: { user }, + error: authError + } = await supabaseClient.auth.getUser(); + + if (authError || !user) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { ...corsHeaders, 'Content-Type': 'application/json' } + }); + } + + const { message, conversationId, systemPrompt } = await req.json(); + + // Check usage limits + const { data: canGenerate } = await supabaseClient.rpc('can_generate', { + target_user_id: user.id, + required_tokens: 2000 + }); + + if (!canGenerate) { + return new Response( + JSON.stringify({ error: 'Monthly token limit exceeded' }), + { + status: 429, + headers: { ...corsHeaders, 'Content-Type': 'application/json' } + } + ); + } + + // Get conversation history + const { data: messages } = await supabaseClient + .from('messages') + .select('*') + .eq('conversation_id', conversationId) + .order('created_at', { ascending: true }) + .limit(20); // Last 20 messages for context + + // Initialize OpenAI + const openai = new OpenAI({ + apiKey: Deno.env.get('OPENAI_API_KEY') + }); + + // Call OpenAI + const completion = await openai.chat.completions.create({ + model: 'gpt-4-turbo-preview', + messages: [ + { + role: 'system', + content: systemPrompt || 'You are Lovable, an AI that helps build web apps.' + }, + ...(messages || []).map((msg: any) => ({ + role: msg.role, + content: msg.content + })), + { + role: 'user', + content: message + } + ], + temperature: 0.7, + max_tokens: 4000 + }); + + const response = completion.choices[0].message.content; + const tokensUsed = completion.usage?.total_tokens || 0; + + // Save user message + await supabaseClient.from('messages').insert({ + conversation_id: conversationId, + role: 'user', + content: message + }); + + // Save assistant message + await supabaseClient.from('messages').insert({ + conversation_id: conversationId, + role: 'assistant', + content: response + }); + + // Track usage + await supabaseClient.from('usage').insert({ + user_id: user.id, + tokens: tokensUsed, + type: 'chat' + }); + + return new Response( + JSON.stringify({ response, tokensUsed }), + { + headers: { ...corsHeaders, 'Content-Type': 'application/json' } + } + ); + } catch (error) { + return new Response( + JSON.stringify({ error: error.message }), + { + status: 500, + headers: { ...corsHeaders, 'Content-Type': 'application/json' } + } + ); + } +}); +``` + +## 2. Code Generation Edge Function + +**File: `supabase/functions/generate-code/index.ts`** + +```typescript +import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.39.0'; +import Anthropic from 'https://esm.sh/@anthropic-ai/sdk@0.17.0'; + +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type' +}; + +serve(async (req) => { + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }); + } + + try { + const supabaseClient = createClient( + Deno.env.get('SUPABASE_URL') ?? '', + Deno.env.get('SUPABASE_ANON_KEY') ?? '', + { + global: { + headers: { Authorization: req.headers.get('Authorization')! } + } + } + ); + + const { + data: { user } + } = await supabaseClient.auth.getUser(); + + if (!user) { + return new Response(JSON.stringify({ error: 'Unauthorized' }), { + status: 401, + headers: { ...corsHeaders, 'Content-Type': 'application/json' } + }); + } + + const { prompt, projectId, componentType } = await req.json(); + + // Load Lovable system prompt from storage + const { data: systemPromptData } = await supabaseClient.storage + .from('system-prompts') + .download('lovable-agent-prompt.txt'); + + const systemPrompt = await systemPromptData?.text(); + + // Get project context + const { data: project } = await supabaseClient + .from('projects') + .select('file_tree, design_system') + .eq('id', projectId) + .single(); + + // Initialize Anthropic (better for code generation) + const anthropic = new Anthropic({ + apiKey: Deno.env.get('ANTHROPIC_API_KEY') + }); + + const message = await anthropic.messages.create({ + model: 'claude-3-5-sonnet-20241022', + max_tokens: 4096, + messages: [ + { + role: 'user', + content: `${systemPrompt} + +## Project Context +File Tree: +\`\`\`json +${JSON.stringify(project?.file_tree, null, 2)} +\`\`\` + +Design System: +\`\`\`json +${JSON.stringify(project?.design_system, null, 2)} +\`\`\` + +## Task +Generate a ${componentType} component: +${prompt} + +Return the complete code with proper imports and exports. +` + } + ] + }); + + const generatedCode = message.content[0].type === 'text' + ? message.content[0].text + : ''; + + // Track usage + await supabaseClient.from('usage').insert({ + user_id: user.id, + tokens: message.usage.input_tokens + message.usage.output_tokens, + type: 'generation' + }); + + return new Response( + JSON.stringify({ + code: generatedCode, + tokensUsed: message.usage.input_tokens + message.usage.output_tokens + }), + { + headers: { ...corsHeaders, 'Content-Type': 'application/json' } + } + ); + } catch (error) { + return new Response( + JSON.stringify({ error: error.message }), + { + status: 500, + headers: { ...corsHeaders, 'Content-Type': 'application/json' } + } + ); + } +}); +``` + +## 3. Deploy Edge Functions + +**File: `supabase/functions/deploy.sh`** + +```bash +#!/bin/bash + +# Deploy all edge functions to Supabase + +echo "πŸš€ Deploying Edge Functions to Supabase..." + +# Deploy chat completion +echo "πŸ“¦ Deploying chat-completion..." +supabase functions deploy chat-completion \ + --no-verify-jwt \ + --project-ref $SUPABASE_PROJECT_REF + +# Deploy code generation +echo "πŸ“¦ Deploying generate-code..." +supabase functions deploy generate-code \ + --no-verify-jwt \ + --project-ref $SUPABASE_PROJECT_REF + +# Set secrets +echo "πŸ” Setting secrets..." +supabase secrets set \ + OPENAI_API_KEY=$OPENAI_API_KEY \ + ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \ + --project-ref $SUPABASE_PROJECT_REF + +echo "βœ… Deployment complete!" +``` + +--- + +# πŸ”— II. WEBHOOK HANDLERS + +## 1. Stripe Webhooks + +**File: `src/app/api/webhooks/stripe/route.ts`** + +```typescript +import { headers } from 'next/headers'; +import { NextResponse } from 'next/server'; +import { createAdminClient } from '@/lib/supabase/server'; +import Stripe from 'stripe'; + +const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { + apiVersion: '2023-10-16' +}); + +const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!; + +export async function POST(req: Request) { + const body = await req.text(); + const signature = (await headers()).get('stripe-signature')!; + + let event: Stripe.Event; + + try { + event = stripe.webhooks.constructEvent(body, signature, webhookSecret); + } catch (err) { + console.error('Webhook signature verification failed:', err); + return NextResponse.json({ error: 'Invalid signature' }, { status: 400 }); + } + + const supabase = createAdminClient(); + + try { + switch (event.type) { + case 'checkout.session.completed': { + const session = event.data.object as Stripe.Checkout.Session; + const userId = session.metadata?.userId; + + if (!userId) break; + + // Update user subscription + await supabase + .from('profiles') + .update({ + subscription_plan: session.metadata?.plan || 'pro', + subscription_status: 'active', + stripe_customer_id: session.customer as string, + stripe_subscription_id: session.subscription as string, + monthly_tokens: session.metadata?.plan === 'pro' ? 200000 : 50000, + monthly_projects: session.metadata?.plan === 'pro' ? 10 : 3 + }) + .eq('id', userId); + + break; + } + + case 'customer.subscription.updated': { + const subscription = event.data.object as Stripe.Subscription; + + await supabase + .from('profiles') + .update({ + subscription_status: subscription.status, + subscription_plan: + subscription.items.data[0].price.metadata.plan || 'pro' + }) + .eq('stripe_subscription_id', subscription.id); + + break; + } + + case 'customer.subscription.deleted': { + const subscription = event.data.object as Stripe.Subscription; + + await supabase + .from('profiles') + .update({ + subscription_status: 'canceled', + subscription_plan: 'free', + monthly_tokens: 50000, + monthly_projects: 3 + }) + .eq('stripe_subscription_id', subscription.id); + + break; + } + + case 'invoice.payment_failed': { + const invoice = event.data.object as Stripe.Invoice; + + await supabase + .from('profiles') + .update({ + subscription_status: 'past_due' + }) + .eq('stripe_customer_id', invoice.customer as string); + + break; + } + } + + return NextResponse.json({ received: true }); + } catch (error) { + console.error('Webhook handler error:', error); + return NextResponse.json( + { error: 'Webhook handler failed' }, + { status: 500 } + ); + } +} +``` + +## 2. GitHub Webhooks + +**File: `src/app/api/webhooks/github/route.ts`** + +```typescript +import { NextRequest, NextResponse } from 'next/server'; +import { createAdminClient } from '@/lib/supabase/server'; +import crypto from 'crypto'; + +export async function POST(req: NextRequest) { + const supabase = createAdminClient(); + + // Verify signature + const signature = req.headers.get('x-hub-signature-256'); + const body = await req.text(); + + const hmac = crypto.createHmac('sha256', process.env.GITHUB_WEBHOOK_SECRET!); + const digest = 'sha256=' + hmac.update(body).digest('hex'); + + if (signature !== digest) { + return NextResponse.json({ error: 'Invalid signature' }, { status: 401 }); + } + + const payload = JSON.parse(body); + const event = req.headers.get('x-github-event'); + + try { + switch (event) { + case 'push': { + // Handle push events (auto-deploy) + const { repository, ref, commits } = payload; + + // Find project linked to this repo + const { data: project } = await supabase + .from('projects') + .select('*') + .eq('github_repo', repository.full_name) + .single(); + + if (project) { + // Trigger deployment + await supabase.from('deployments').insert({ + project_id: project.id, + provider: 'vercel', + status: 'pending', + url: '' + }); + + // You can trigger Vercel deployment here + // await triggerVercelDeployment(project); + } + + break; + } + + case 'pull_request': { + const { action, pull_request } = payload; + + if (action === 'opened' || action === 'synchronize') { + // Create preview deployment for PR + // await createPreviewDeployment(pull_request); + } + + break; + } + } + + return NextResponse.json({ received: true }); + } catch (error) { + console.error('GitHub webhook error:', error); + return NextResponse.json( + { error: 'Webhook handler failed' }, + { status: 500 } + ); + } +} +``` + +## 3. Vercel Deploy Webhook + +**File: `src/app/api/webhooks/vercel/route.ts`** + +```typescript +import { NextRequest, NextResponse } from 'next/server'; +import { createAdminClient } from '@/lib/supabase/server'; + +export async function POST(req: NextRequest) { + const supabase = createAdminClient(); + const payload = await req.json(); + + try { + const { deployment, type } = payload; + + // Find deployment in database + const { data: existingDeployment } = await supabase + .from('deployments') + .select('*') + .eq('url', deployment.url) + .single(); + + if (!existingDeployment) { + return NextResponse.json({ received: true }); + } + + let status = 'pending'; + let buildLogs = ''; + + switch (type) { + case 'deployment.created': + status = 'building'; + break; + case 'deployment.ready': + status = 'ready'; + break; + case 'deployment.error': + status = 'error'; + buildLogs = deployment.errorMessage || 'Deployment failed'; + break; + } + + // Update deployment status + await supabase + .from('deployments') + .update({ + status, + build_logs: buildLogs, + updated_at: new Date().toISOString() + }) + .eq('id', existingDeployment.id); + + // Notify user via realtime + await supabase + .from('deployments') + .update({ updated_at: new Date().toISOString() }) + .eq('id', existingDeployment.id); + + return NextResponse.json({ received: true }); + } catch (error) { + console.error('Vercel webhook error:', error); + return NextResponse.json( + { error: 'Webhook handler failed' }, + { status: 500 } + ); + } +} +``` + +--- + +# πŸ§ͺ III. TESTING SETUP + +## 1. Vitest Configuration + +**File: `vitest.config.ts`** + +```typescript +import { defineConfig } from 'vitest/config'; +import react from '@vitejs/plugin-react'; +import path from 'path'; + +export default defineConfig({ + plugins: [react()], + test: { + environment: 'jsdom', + setupFiles: ['./src/test/setup.ts'], + globals: true, + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + exclude: [ + 'node_modules/', + 'src/test/', + '**/*.d.ts', + '**/*.config.*', + '**/mockData' + ] + } + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + } +}); +``` + +**File: `src/test/setup.ts`** + +```typescript +import { expect, afterEach, vi } from 'vitest'; +import { cleanup } from '@testing-library/react'; +import * as matchers from '@testing-library/jest-dom/matchers'; + +expect.extend(matchers); + +// Cleanup after each test +afterEach(() => { + cleanup(); +}); + +// Mock Supabase client +vi.mock('@/lib/supabase/client', () => ({ + createClient: () => ({ + auth: { + getUser: vi.fn(), + signIn: vi.fn(), + signOut: vi.fn() + }, + from: vi.fn(() => ({ + select: vi.fn().mockReturnThis(), + insert: vi.fn().mockReturnThis(), + update: vi.fn().mockReturnThis(), + delete: vi.fn().mockReturnThis(), + eq: vi.fn().mockReturnThis(), + single: vi.fn() + })) + }) +})); + +// Mock Next.js router +vi.mock('next/navigation', () => ({ + useRouter: () => ({ + push: vi.fn(), + replace: vi.fn(), + prefetch: vi.fn() + }), + usePathname: () => '/', + useSearchParams: () => new URLSearchParams() +})); +``` + +## 2. Component Tests + +**File: `src/components/chat/__tests__/chat-panel.test.tsx`** + +```typescript +import { describe, it, expect, vi } from 'vitest'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { ChatPanel } from '../chat-panel'; + +describe('ChatPanel', () => { + it('renders chat input', () => { + render(); + + expect( + screen.getByPlaceholderText(/describe what you want to build/i) + ).toBeInTheDocument(); + }); + + it('sends message on button click', async () => { + const { getByRole, getByPlaceholderText } = render( + + ); + + const input = getByPlaceholderText(/describe what you want to build/i); + const button = getByRole('button', { name: /send/i }); + + fireEvent.change(input, { target: { value: 'Create a button' } }); + fireEvent.click(button); + + await waitFor(() => { + expect(screen.getByText('Create a button')).toBeInTheDocument(); + }); + }); + + it('disables input while loading', async () => { + const { getByPlaceholderText, getByRole } = render( + + ); + + const input = getByPlaceholderText(/describe what you want to build/i); + const button = getByRole('button'); + + fireEvent.change(input, { target: { value: 'Test' } }); + fireEvent.click(button); + + expect(input).toBeDisabled(); + }); +}); +``` + +## 3. E2E Tests with Playwright + +**File: `playwright.config.ts`** + +```typescript +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './e2e', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + baseURL: 'http://localhost:3000', + trace: 'on-first-retry' + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] } + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] } + }, + { + name: 'webkit', + use: { ...devices['Desktop Safari'] } + } + ], + webServer: { + command: 'npm run dev', + url: 'http://localhost:3000', + reuseExistingServer: !process.env.CI + } +}); +``` + +**File: `e2e/auth.spec.ts`** + +```typescript +import { test, expect } from '@playwright/test'; + +test.describe('Authentication', () => { + test('user can sign up', async ({ page }) => { + await page.goto('/signup'); + + await page.fill('input[type="email"]', 'test@example.com'); + await page.fill('input[type="password"]', 'password123'); + await page.fill('input[name="fullName"]', 'Test User'); + + await page.click('button[type="submit"]'); + + await expect(page).toHaveURL('/'); + }); + + test('user can sign in', async ({ page }) => { + await page.goto('/login'); + + await page.fill('input[type="email"]', 'test@example.com'); + await page.fill('input[type="password"]', 'password123'); + + await page.click('button[type="submit"]'); + + await expect(page).toHaveURL('/'); + }); +}); + +test.describe('Project Management', () => { + test.beforeEach(async ({ page }) => { + // Login first + await page.goto('/login'); + await page.fill('input[type="email"]', 'test@example.com'); + await page.fill('input[type="password"]', 'password123'); + await page.click('button[type="submit"]'); + await page.waitForURL('/'); + }); + + test('user can create a project', async ({ page }) => { + await page.click('text=New Project'); + + await expect(page).toHaveURL(/\/project\/.+/); + await expect(page.locator('text=Chat')).toBeVisible(); + }); + + test('user can send chat message', async ({ page }) => { + await page.click('text=New Project'); + + const textarea = page.locator('textarea'); + await textarea.fill('Create a button component'); + await page.click('button:has-text("Send")'); + + await expect( + page.locator('text=Create a button component') + ).toBeVisible(); + }); +}); +``` + +--- + +_TiαΊΏp tα»₯c vα»›i CI/CD, Monitoring, vΓ  Advanced Features..._ diff --git a/LOVABLE_CLONE_DEPLOYMENT_GUIDE.md b/LOVABLE_CLONE_DEPLOYMENT_GUIDE.md new file mode 100644 index 00000000..cbd20dfa --- /dev/null +++ b/LOVABLE_CLONE_DEPLOYMENT_GUIDE.md @@ -0,0 +1,801 @@ +# 🚒 Lovable Clone - Complete Deployment Guide + +> Step-by-step production deployment vα»›i Vercel + Supabase + +--- + +# πŸ“‘ Table of Contents + +1. [Pre-Deployment Checklist](#pre-deployment-checklist) +2. [Supabase Production Setup](#supabase-production-setup) +3. [Vercel Deployment](#vercel-deployment) +4. [Domain & SSL](#domain--ssl) +5. [Environment Variables](#environment-variables) +6. [Database Backups](#database-backups) +7. [Monitoring Setup](#monitoring-setup) +8. [Post-Deployment](#post-deployment) + +--- + +# βœ… I. PRE-DEPLOYMENT CHECKLIST + +## 1. Code Quality + +```bash +# Run all checks +npm run lint +npm run type-check +npm run test:unit +npm run test:e2e +npm run build + +# Check bundle size +npx @next/bundle-analyzer +``` + +## 2. Security Audit + +```bash +# Check for vulnerabilities +npm audit + +# Fix if possible +npm audit fix + +# Check for outdated packages +npm outdated + +# Update safely +npm update +``` + +## 3. Performance Audit + +```bash +# Run Lighthouse +npx lighthouse https://your-staging-url.vercel.app \ + --output=html \ + --output-path=./lighthouse-report.html + +# Aim for: +# - Performance: > 90 +# - Accessibility: > 95 +# - Best Practices: > 95 +# - SEO: > 95 +``` + +## 4. Configuration Checklist + +- [ ] All environment variables configured +- [ ] Database migrations applied +- [ ] RLS policies enabled +- [ ] Storage buckets created +- [ ] Realtime enabled +- [ ] Email templates configured +- [ ] Webhook endpoints ready +- [ ] Rate limiting configured +- [ ] Analytics integrated +- [ ] Error tracking setup +- [ ] Backup strategy in place + +--- + +# πŸ—„οΈ II. SUPABASE PRODUCTION SETUP + +## 1. Create Production Project + +```bash +# Go to https://supabase.com +# Click "New Project" +# Choose: +# - Organization +# - Project name: lovable-production +# - Database password: Use strong password (save it!) +# - Region: Choose closest to users +# - Pricing plan: Pro (recommended) +``` + +## 2. Run Database Migrations + +```sql +-- Copy from supabase/migrations/00000000000000_initial_schema.sql +-- Paste into SQL Editor +-- Click "Run" + +-- Verify tables created +SELECT table_name +FROM information_schema.tables +WHERE table_schema = 'public'; + +-- Check RLS enabled +SELECT tablename, rowsecurity +FROM pg_tables +WHERE schemaname = 'public'; +``` + +## 3. Configure Authentication + +```bash +# In Supabase Dashboard: +# Authentication > Providers + +# Enable Email +- Confirm Email: ON +- Double Confirm: OFF +- Secure Email Change: ON + +# Enable Google OAuth +- Client ID: [Google Cloud Console] +- Client Secret: [Google Cloud Console] +- Redirect URL: https://[project-ref].supabase.co/auth/v1/callback + +# Enable GitHub OAuth +- Client ID: [GitHub OAuth Apps] +- Client Secret: [GitHub OAuth Apps] +- Redirect URL: https://[project-ref].supabase.co/auth/v1/callback +``` + +## 4. Setup Storage + +```bash +# Storage > Create Bucket + +Bucket name: project-assets +Public: Yes +File size limit: 10 MB +Allowed MIME types: image/*, application/zip + +# Set RLS policies for bucket +CREATE POLICY "Users can upload own files" +ON storage.objects FOR INSERT +WITH CHECK ( + bucket_id = 'project-assets' AND + auth.uid()::text = (storage.foldername(name))[1] +); + +CREATE POLICY "Anyone can view files" +ON storage.objects FOR SELECT +USING (bucket_id = 'project-assets'); + +CREATE POLICY "Users can delete own files" +ON storage.objects FOR DELETE +USING ( + bucket_id = 'project-assets' AND + auth.uid()::text = (storage.foldername(name))[1] +); +``` + +## 5. Enable Realtime + +```sql +-- Enable realtime for tables +ALTER PUBLICATION supabase_realtime ADD TABLE messages; +ALTER PUBLICATION supabase_realtime ADD TABLE project_files; +ALTER PUBLICATION supabase_realtime ADD TABLE deployments; + +-- Verify +SELECT schemaname, tablename +FROM pg_publication_tables +WHERE pubname = 'supabase_realtime'; +``` + +## 6. Deploy Edge Functions + +```bash +# Install Supabase CLI +npm install -g supabase + +# Login +supabase login + +# Link production project +supabase link --project-ref your-production-ref + +# Deploy functions +supabase functions deploy chat-completion +supabase functions deploy generate-code + +# Set secrets +supabase secrets set \ + OPENAI_API_KEY=sk-... \ + ANTHROPIC_API_KEY=sk-ant-... \ + --project-ref your-production-ref +``` + +--- + +# πŸš€ III. VERCEL DEPLOYMENT + +## 1. Connect Repository + +```bash +# Go to https://vercel.com +# Click "Add New Project" +# Import Git Repository +# Select your GitHub repo +``` + +## 2. Configure Build Settings + +``` +Framework Preset: Next.js +Root Directory: ./ +Build Command: npm run build +Output Directory: .next +Install Command: npm ci + +Node.js Version: 18.x +``` + +## 3. Environment Variables + +```env +# Production environment variables +NEXT_PUBLIC_SUPABASE_URL=https://[your-ref].supabase.co +NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ... + +# Service role (for admin operations) +SUPABASE_SERVICE_ROLE_KEY=eyJ... + +# AI Keys +AI_PROVIDER=openai +OPENAI_API_KEY=sk-... +# OR +ANTHROPIC_API_KEY=sk-ant-... + +# Stripe +STRIPE_SECRET_KEY=sk_live_... +STRIPE_WEBHOOK_SECRET=whsec_... +NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_... + +# Email +RESEND_API_KEY=re_... +FROM_EMAIL=noreply@yourdomain.com + +# Monitoring +NEXT_PUBLIC_SENTRY_DSN=https://...@sentry.io/... +SENTRY_AUTH_TOKEN=sntrys_... +SENTRY_ORG=your-org +SENTRY_PROJECT=lovable-clone + +# Analytics +NEXT_PUBLIC_POSTHOG_KEY=phc_... +NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com + +# Rate Limiting +UPSTASH_REDIS_REST_URL=https://...upstash.io +UPSTASH_REDIS_REST_TOKEN=... + +# GitHub Integration (optional) +GITHUB_TOKEN=ghp_... +GITHUB_WEBHOOK_SECRET=... + +# Deployment +VERCEL_TOKEN=... +NETLIFY_TOKEN=... +``` + +## 4. Deploy + +```bash +# First deployment +git push origin main + +# Or manual deploy +vercel --prod + +# Check deployment +vercel ls +``` + +## 5. Post-Deploy Verification + +```bash +# Test endpoints +curl https://your-domain.com/api/health + +# Check SSR +curl https://your-domain.com + +# Test authentication +# Visit https://your-domain.com/login + +# Test Supabase connection +# Try signing up a user + +# Check Realtime +# Open browser console, should see WebSocket connection +``` + +--- + +# 🌐 IV. DOMAIN & SSL + +## 1. Add Custom Domain + +```bash +# In Vercel Dashboard: +# Settings > Domains > Add + +# Add your domain: +yourdomain.com +www.yourdomain.com + +# Vercel will provide DNS records +``` + +## 2. Configure DNS + +``` +# Add these records to your DNS provider: + +Type: A +Name: @ +Value: 76.76.21.21 + +Type: CNAME +Name: www +Value: cname.vercel-dns.com + +# For Supabase custom domain (optional): +Type: CNAME +Name: api +Value: [your-ref].supabase.co +``` + +## 3. SSL Certificate + +``` +# Vercel automatically provisions SSL +# Check in Vercel Dashboard > Domains +# Should show: "SSL Certificate Valid" + +# Force HTTPS redirect +# Already handled by next.config.js headers +``` + +--- + +# πŸ” V. ENVIRONMENT MANAGEMENT + +## 1. Vercel Environment Setup + +```bash +# Production +NEXT_PUBLIC_SUPABASE_URL=production-url +SUPABASE_SERVICE_ROLE_KEY=production-key + +# Preview (for PRs) +NEXT_PUBLIC_SUPABASE_URL=staging-url +SUPABASE_SERVICE_ROLE_KEY=staging-key + +# Development (local) +NEXT_PUBLIC_SUPABASE_URL=local-url +SUPABASE_SERVICE_ROLE_KEY=local-key +``` + +## 2. Secrets Management + +```bash +# Use Vercel CLI to set secrets +vercel env add STRIPE_SECRET_KEY production + +# Or use Vercel Dashboard +# Settings > Environment Variables + +# Never commit secrets to git! +# Add to .gitignore: +.env*.local +.env.production +``` + +--- + +# πŸ’Ύ VI. DATABASE BACKUPS + +## 1. Automated Backups (Supabase Pro) + +```bash +# Supabase automatically backs up: +# - Daily backups: 7 days retention +# - Weekly backups: 4 weeks retention +# - Monthly backups: 3 months retention + +# Enable in Supabase Dashboard: +# Database > Backups > Enable +``` + +## 2. Manual Backup Script + +```bash +#!/bin/bash +# backup.sh + +DATE=$(date +%Y%m%d_%H%M%S) +BACKUP_DIR="./backups" +PROJECT_REF="your-project-ref" + +mkdir -p $BACKUP_DIR + +# Backup database +supabase db dump \ + --project-ref $PROJECT_REF \ + --password $DB_PASSWORD \ + > $BACKUP_DIR/db_$DATE.sql + +# Backup storage +supabase storage download \ + --project-ref $PROJECT_REF \ + --bucket project-assets \ + --output $BACKUP_DIR/storage_$DATE.tar.gz + +# Upload to S3 (optional) +aws s3 cp $BACKUP_DIR/ s3://your-backup-bucket/ --recursive + +echo "Backup completed: $DATE" +``` + +## 3. Point-in-Time Recovery + +```sql +-- Supabase Pro includes PITR +-- Can restore to any point in last 7 days + +-- To restore: +-- 1. Go to Supabase Dashboard +-- 2. Database > Backups +-- 3. Click "Restore" +-- 4. Select timestamp +-- 5. Confirm +``` + +--- + +# πŸ“Š VII. MONITORING SETUP + +## 1. Vercel Analytics + +```typescript +// Already enabled in layout.tsx +import { Analytics } from '@vercel/analytics/react'; + +export default function RootLayout({ children }) { + return ( + + + {children} + + + + ); +} +``` + +## 2. Sentry Setup + +```bash +# Install Sentry +npm install @sentry/nextjs + +# Run Sentry wizard +npx @sentry/wizard@latest -i nextjs + +# Configure in sentry.client.config.ts +Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + tracesSampleRate: 1.0, + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0 +}); +``` + +## 3. Uptime Monitoring + +```bash +# Use services like: +# - UptimeRobot (https://uptimerobot.com) +# - Pingdom +# - Better Uptime + +# Monitor endpoints: +- https://yourdomain.com (main site) +- https://yourdomain.com/api/health (API health) +- https://[ref].supabase.co (database) +``` + +## 4. Performance Monitoring + +```bash +# Lighthouse CI +npm install -g @lhci/cli + +# Create lighthouserc.js +module.exports = { + ci: { + collect: { + url: ['https://yourdomain.com'], + numberOfRuns: 3 + }, + assert: { + assertions: { + 'categories:performance': ['error', { minScore: 0.9 }], + 'categories:accessibility': ['error', { minScore: 0.95 }] + } + }, + upload: { + target: 'temporary-public-storage' + } + } +}; + +# Run in CI +lhci autorun +``` + +--- + +# 🎯 VIII. POST-DEPLOYMENT + +## 1. Smoke Tests + +```bash +# Test critical paths +curl -f https://yourdomain.com || exit 1 +curl -f https://yourdomain.com/api/health || exit 1 + +# Test authentication +# - Sign up new user +# - Sign in +# - Create project +# - Send chat message +# - Generate code +``` + +## 2. Performance Baseline + +```bash +# Run Lighthouse +# Save scores for comparison + +Performance: ___ +Accessibility: ___ +Best Practices: ___ +SEO: ___ +``` + +## 3. Set Up Alerts + +```yaml +# alerts.yml +alerts: + - name: Error Rate High + condition: error_rate > 5% + notify: email, slack + + - name: Response Time Slow + condition: p95_response_time > 2s + notify: email + + - name: Database Connection Issues + condition: db_connection_failures > 0 + notify: pagerduty + + - name: High Traffic + condition: requests_per_minute > 10000 + notify: slack +``` + +## 4. Documentation + +```markdown +# Create DEPLOYMENT.md + +## Production URLs + +- Main Site: https://yourdomain.com +- API: https://yourdomain.com/api +- Dashboard: https://yourdomain.com/dashboard + +## Deployment Process + +1. Create PR +2. Wait for CI checks +3. Merge to main +4. Automatic deploy to production +5. Run smoke tests +6. Monitor for errors + +## Rollback Procedure + +1. Go to Vercel dashboard +2. Find previous deployment +3. Click "Promote to Production" +4. Verify deployment + +## Emergency Contacts + +- On-call: +1-xxx-xxx-xxxx +- Slack: #alerts +- Email: oncall@company.com +``` + +--- + +# πŸ”₯ IX. DISASTER RECOVERY + +## 1. Incident Response Plan + +```markdown +## Severity Levels + +**P0 - Critical** + +- Site completely down +- Data loss occurring +- Security breach +- Response time: Immediate + +**P1 - High** + +- Major features broken +- Performance degraded >50% +- Response time: 15 minutes + +**P2 - Medium** + +- Minor features broken +- Performance degraded <50% +- Response time: 2 hours + +**P3 - Low** + +- Cosmetic issues +- Response time: 24 hours +``` + +## 2. Recovery Procedures + +```bash +# Database Recovery +1. Identify issue +2. Stop writes if needed +3. Restore from backup +4. Verify data integrity +5. Resume normal operations + +# Application Recovery +1. Roll back deployment +2. Check logs +3. Fix issue +4. Deploy fix +5. Verify + +# Data Center Failover +1. Switch DNS to backup region +2. Activate read replicas +3. Restore write capability +4. Monitor performance +``` + +## 3. Communication Plan + +``` +Internal: +- Post in #incidents Slack channel +- Email stakeholders +- Update status page + +External: +- Update status.yourdomain.com +- Tweet from @yourcompany +- Email affected users +``` + +--- + +# πŸ“‹ X. PRODUCTION CHECKLIST + +## Launch Day + +- [ ] All tests passing +- [ ] Database backups verified +- [ ] Monitoring alerts configured +- [ ] Error tracking working +- [ ] Analytics tracking +- [ ] SSL certificate valid +- [ ] Custom domain configured +- [ ] Email sending works +- [ ] Webhooks configured +- [ ] Rate limiting active +- [ ] RLS policies enabled +- [ ] API keys secured +- [ ] Documentation updated +- [ ] Team trained +- [ ] Support ready + +## Week 1 + +- [ ] Monitor error rates +- [ ] Check performance metrics +- [ ] Review user feedback +- [ ] Optimize slow queries +- [ ] Address quick wins +- [ ] Update documentation + +## Month 1 + +- [ ] Review analytics +- [ ] Plan improvements +- [ ] Security audit +- [ ] Cost optimization +- [ ] Scale planning + +--- + +# πŸŽ“ XI. MAINTENANCE + +## Daily + +```bash +# Check dashboards +- Vercel Analytics +- Sentry errors +- Supabase logs +- PostHog events + +# Review metrics +- Active users +- Error rate +- Response time +- Database size +``` + +## Weekly + +```bash +# Update dependencies +npm update + +# Review performance +- Lighthouse scores +- Web Vitals +- Bundle size + +# Check backups +- Verify last backup +- Test restore +``` + +## Monthly + +```bash +# Security audit +npm audit +npm outdated + +# Cost review +- Vercel usage +- Supabase usage +- Third-party services + +# Capacity planning +- Database growth +- Storage usage +- API calls +``` + +--- + +**πŸŽ‰ Congratulations! Your Lovable Clone is now production-ready and deployed!** + +## Support Resources + +- **Vercel Docs**: https://vercel.com/docs +- **Supabase Docs**: https://supabase.com/docs +- **Next.js Docs**: https://nextjs.org/docs + +## Need Help? + +- GitHub Issues: https://github.com/your-repo/issues +- Discord: https://discord.gg/your-server +- Email: support@yourdomain.com diff --git a/LOVABLE_CLONE_PRODUCTION_READY.md b/LOVABLE_CLONE_PRODUCTION_READY.md new file mode 100644 index 00000000..9a4f3699 --- /dev/null +++ b/LOVABLE_CLONE_PRODUCTION_READY.md @@ -0,0 +1,937 @@ +# 🏭 Lovable Clone - Production Ready Setup + +> CI/CD, Monitoring, Security, Performance, vΓ  Deployment Guide + +--- + +# πŸ“‘ Table of Contents + +1. [CI/CD Pipeline](#cicd-pipeline) +2. [Monitoring & Analytics](#monitoring--analytics) +3. [Security Best Practices](#security-best-practices) +4. [Performance Optimization](#performance-optimization) +5. [Production Deployment](#production-deployment) +6. [Disaster Recovery](#disaster-recovery) + +--- + +# πŸ”„ I. CI/CD PIPELINE + +## 1. GitHub Actions Workflow + +**File: `.github/workflows/ci.yml`** + +```yaml +name: CI/CD Pipeline + +on: + push: + branches: [main, develop] + pull_request: + branches: [main, develop] + +env: + NODE_VERSION: '18.x' + +jobs: + lint-and-type-check: + name: Lint & Type Check + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run ESLint + run: npm run lint + + - name: Run TypeScript check + run: npx tsc --noEmit + + test: + name: Test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run unit tests + run: npm run test:unit + + - name: Upload coverage + uses: codecov/codecov-action@v3 + with: + files: ./coverage/coverage-final.json + flags: unittests + + e2e-test: + name: E2E Tests + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Install Playwright + run: npx playwright install --with-deps + + - name: Run E2E tests + run: npm run test:e2e + env: + NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SUPABASE_URL }} + NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} + + - name: Upload test results + uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + + build: + name: Build + runs-on: ubuntu-latest + needs: [lint-and-type-check, test] + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build application + run: npm run build + env: + NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SUPABASE_URL }} + NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} + + - name: Upload build artifacts + uses: actions/upload-artifact@v3 + with: + name: build + path: .next/ + + deploy-staging: + name: Deploy to Staging + runs-on: ubuntu-latest + needs: [build, e2e-test] + if: github.ref == 'refs/heads/develop' + + steps: + - uses: actions/checkout@v4 + + - name: Deploy to Vercel Staging + uses: amondnet/vercel-action@v25 + with: + vercel-token: ${{ secrets.VERCEL_TOKEN }} + vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} + vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} + scope: ${{ secrets.VERCEL_ORG_ID }} + + deploy-production: + name: Deploy to Production + runs-on: ubuntu-latest + needs: [build, e2e-test] + if: github.ref == 'refs/heads/main' + + steps: + - uses: actions/checkout@v4 + + - name: Deploy to Vercel Production + uses: amondnet/vercel-action@v25 + with: + vercel-token: ${{ secrets.VERCEL_TOKEN }} + vercel-org-id: ${{ secrets.VERCEL_ORG_ID }} + vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }} + vercel-args: '--prod' + scope: ${{ secrets.VERCEL_ORG_ID }} + + - name: Create Sentry release + uses: getsentry/action-release@v1 + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} + with: + environment: production +``` + +## 2. Pre-commit Hooks + +**File: `.husky/pre-commit`** + +```bash +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +# Run lint-staged +npx lint-staged + +# Run type check +npm run type-check +``` + +**File: `.husky/commit-msg`** + +```bash +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +# Validate commit message +npx --no -- commitlint --edit ${1} +``` + +**File: `package.json` (add scripts)** + +```json +{ + "scripts": { + "prepare": "husky install", + "test:unit": "vitest run", + "test:e2e": "playwright test", + "test:watch": "vitest", + "type-check": "tsc --noEmit", + "lint": "next lint", + "lint:fix": "next lint --fix", + "format": "prettier --write \"**/*.{ts,tsx,md}\"" + }, + "lint-staged": { + "*.{ts,tsx}": [ + "eslint --fix", + "prettier --write" + ], + "*.{md,json}": [ + "prettier --write" + ] + } +} +``` + +## 3. Commitlint Configuration + +**File: `commitlint.config.js`** + +```javascript +module.exports = { + extends: ['@commitlint/config-conventional'], + rules: { + 'type-enum': [ + 2, + 'always', + [ + 'feat', // New feature + 'fix', // Bug fix + 'docs', // Documentation + 'style', // Formatting + 'refactor', // Code restructuring + 'perf', // Performance + 'test', // Tests + 'chore', // Maintenance + 'ci', // CI/CD + 'build' // Build system + ] + ] + } +}; +``` + +--- + +# πŸ“Š II. MONITORING & ANALYTICS + +## 1. Sentry Error Tracking + +**File: `src/lib/sentry.ts`** + +```typescript +import * as Sentry from '@sentry/nextjs'; + +export function initSentry() { + if (process.env.NEXT_PUBLIC_SENTRY_DSN) { + Sentry.init({ + dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, + environment: process.env.NODE_ENV, + tracesSampleRate: 1.0, + + // Performance Monitoring + integrations: [ + new Sentry.BrowserTracing({ + tracePropagationTargets: [ + 'localhost', + /^https:\/\/.*\.vercel\.app/ + ] + }), + new Sentry.Replay({ + maskAllText: true, + blockAllMedia: true + }) + ], + + // Session Replay + replaysSessionSampleRate: 0.1, + replaysOnErrorSampleRate: 1.0, + + beforeSend(event, hint) { + // Filter out sensitive data + if (event.request) { + delete event.request.cookies; + delete event.request.headers; + } + return event; + } + }); + } +} + +// Custom error logging +export function logError(error: Error, context?: Record) { + console.error(error); + + if (process.env.NEXT_PUBLIC_SENTRY_DSN) { + Sentry.captureException(error, { + extra: context + }); + } +} + +// Performance monitoring +export function startTransaction(name: string, op: string) { + return Sentry.startTransaction({ name, op }); +} +``` + +**File: `src/app/layout.tsx` (add Sentry)** + +```typescript +import { initSentry } from '@/lib/sentry'; + +// Initialize Sentry +if (typeof window !== 'undefined') { + initSentry(); +} + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} +``` + +## 2. PostHog Analytics + +**File: `src/lib/posthog.ts`** + +```typescript +import posthog from 'posthog-js'; + +export function initPostHog() { + if ( + typeof window !== 'undefined' && + process.env.NEXT_PUBLIC_POSTHOG_KEY + ) { + posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, { + api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://app.posthog.com', + loaded: (posthog) => { + if (process.env.NODE_ENV === 'development') { + posthog.debug(); + } + }, + capture_pageview: false, // We'll manually track + capture_pageleave: true, + autocapture: { + dom_event_allowlist: ['click', 'submit'], + element_allowlist: ['button', 'a'] + } + }); + } +} + +export function trackEvent( + eventName: string, + properties?: Record +) { + if (typeof window !== 'undefined') { + posthog.capture(eventName, properties); + } +} + +export function identifyUser(userId: string, traits?: Record) { + if (typeof window !== 'undefined') { + posthog.identify(userId, traits); + } +} + +export function trackPageView() { + if (typeof window !== 'undefined') { + posthog.capture('$pageview'); + } +} +``` + +**File: `src/components/analytics/posthog-provider.tsx`** + +```typescript +'use client'; + +import { useEffect } from 'react'; +import { usePathname, useSearchParams } from 'next/navigation'; +import { initPostHog, trackPageView } from '@/lib/posthog'; + +export function PostHogProvider({ children }: { children: React.ReactNode }) { + const pathname = usePathname(); + const searchParams = useSearchParams(); + + useEffect(() => { + initPostHog(); + }, []); + + useEffect(() => { + trackPageView(); + }, [pathname, searchParams]); + + return <>{children}; +} +``` + +## 3. Performance Monitoring + +**File: `src/lib/performance.ts`** + +```typescript +import { Metric } from 'web-vitals'; + +export function sendToAnalytics(metric: Metric) { + // Send to PostHog + if (typeof window !== 'undefined' && window.posthog) { + window.posthog.capture('web_vitals', { + metric_name: metric.name, + metric_value: metric.value, + metric_id: metric.id, + metric_rating: metric.rating + }); + } + + // Send to Vercel Analytics + if (process.env.NEXT_PUBLIC_VERCEL_ENV) { + const body = JSON.stringify({ + dsn: process.env.NEXT_PUBLIC_VERCEL_ANALYTICS_ID, + id: metric.id, + page: window.location.pathname, + href: window.location.href, + event_name: metric.name, + value: metric.value.toString(), + speed: navigator?.connection?.effectiveType || '' + }); + + const url = 'https://vitals.vercel-insights.com/v1/vitals'; + + if (navigator.sendBeacon) { + navigator.sendBeacon(url, body); + } else { + fetch(url, { body, method: 'POST', keepalive: true }); + } + } +} + +// Log slow renders +export function logSlowRender(componentName: string, renderTime: number) { + if (renderTime > 16) { + // More than 1 frame (60fps) + console.warn(`Slow render: ${componentName} took ${renderTime}ms`); + + if (typeof window !== 'undefined' && window.posthog) { + window.posthog.capture('slow_render', { + component: componentName, + render_time: renderTime + }); + } + } +} +``` + +**File: `src/app/layout.tsx` (add Web Vitals)** + +```typescript +'use client'; + +import { useReportWebVitals } from 'next/web-vitals'; +import { sendToAnalytics } from '@/lib/performance'; + +export function WebVitals() { + useReportWebVitals((metric) => { + sendToAnalytics(metric); + }); + + return null; +} +``` + +## 4. Logging System + +**File: `src/lib/logger.ts`** + +```typescript +import pino from 'pino'; + +const logger = pino({ + level: process.env.LOG_LEVEL || 'info', + formatters: { + level: (label) => { + return { level: label }; + } + }, + redact: { + paths: ['password', 'apiKey', 'token'], + remove: true + }, + ...(process.env.NODE_ENV === 'production' + ? { + // Structured logging for production + transport: { + target: 'pino/file', + options: { destination: 1 } // stdout + } + } + : { + // Pretty printing for development + transport: { + target: 'pino-pretty', + options: { + colorize: true + } + } + }) +}); + +export { logger }; + +// Usage: +// logger.info({ userId: '123', action: 'login' }, 'User logged in'); +// logger.error({ err, context }, 'Error occurred'); +``` + +--- + +# πŸ”’ III. SECURITY BEST PRACTICES + +## 1. Security Headers + +**File: `next.config.js`** + +```javascript +/** @type {import('next').NextConfig} */ +const nextConfig = { + async headers() { + return [ + { + source: '/:path*', + headers: [ + { + key: 'X-DNS-Prefetch-Control', + value: 'on' + }, + { + key: 'Strict-Transport-Security', + value: 'max-age=63072000; includeSubDomains; preload' + }, + { + key: 'X-Frame-Options', + value: 'SAMEORIGIN' + }, + { + key: 'X-Content-Type-Options', + value: 'nosniff' + }, + { + key: 'X-XSS-Protection', + value: '1; mode=block' + }, + { + key: 'Referrer-Policy', + value: 'strict-origin-when-cross-origin' + }, + { + key: 'Permissions-Policy', + value: 'camera=(), microphone=(), geolocation=()' + }, + { + key: 'Content-Security-Policy', + value: [ + "default-src 'self'", + "script-src 'self' 'unsafe-eval' 'unsafe-inline' https://cdn.jsdelivr.net", + "style-src 'self' 'unsafe-inline'", + "img-src 'self' data: https:", + "font-src 'self' data:", + "connect-src 'self' https://*.supabase.co wss://*.supabase.co", + "frame-src 'self'" + ].join('; ') + } + ] + } + ]; + }, + + // Enable React Strict Mode + reactStrictMode: true, + + // Remove powered by header + poweredByHeader: false, + + // Compression + compress: true, + + // Image optimization + images: { + domains: ['*.supabase.co'], + formats: ['image/avif', 'image/webp'] + } +}; + +module.exports = nextConfig; +``` + +## 2. Rate Limiting + +**File: `src/lib/rate-limit.ts`** + +```typescript +import { Ratelimit } from '@upstash/ratelimit'; +import { Redis } from '@upstash/redis'; + +// Create Redis client +const redis = new Redis({ + url: process.env.UPSTASH_REDIS_REST_URL!, + token: process.env.UPSTASH_REDIS_REST_TOKEN! +}); + +// Create rate limiter +export const ratelimit = new Ratelimit({ + redis, + limiter: Ratelimit.slidingWindow(10, '10 s'), // 10 requests per 10 seconds + analytics: true +}); + +// Custom rate limits +export const aiRatelimit = new Ratelimit({ + redis, + limiter: Ratelimit.slidingWindow(3, '60 s'), // 3 AI requests per minute + analytics: true +}); + +export const authRatelimit = new Ratelimit({ + redis, + limiter: Ratelimit.slidingWindow(5, '60 s'), // 5 auth attempts per minute + analytics: true +}); +``` + +**File: `src/middleware.ts` (add rate limiting)** + +```typescript +import { NextResponse } from 'next/server'; +import type { NextRequest } from 'next/server'; +import { ratelimit } from '@/lib/rate-limit'; + +export async function middleware(request: NextRequest) { + // Rate limiting for API routes + if (request.nextUrl.pathname.startsWith('/api/')) { + const ip = request.ip ?? '127.0.0.1'; + const { success, limit, reset, remaining } = await ratelimit.limit(ip); + + if (!success) { + return NextResponse.json( + { error: 'Too many requests' }, + { + status: 429, + headers: { + 'X-RateLimit-Limit': limit.toString(), + 'X-RateLimit-Remaining': remaining.toString(), + 'X-RateLimit-Reset': reset.toString() + } + } + ); + } + } + + return NextResponse.next(); +} +``` + +## 3. Input Validation + +**File: `src/lib/validation.ts`** + +```typescript +import { z } from 'zod'; + +// User schemas +export const signUpSchema = z.object({ + email: z.string().email('Invalid email address'), + password: z + .string() + .min(8, 'Password must be at least 8 characters') + .regex( + /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/, + 'Password must contain uppercase, lowercase, and number' + ), + fullName: z.string().min(2, 'Name must be at least 2 characters') +}); + +export const signInSchema = z.object({ + email: z.string().email('Invalid email address'), + password: z.string().min(1, 'Password is required') +}); + +// Project schemas +export const createProjectSchema = z.object({ + name: z.string().min(1).max(100), + description: z.string().max(500).optional(), + framework: z.enum(['next', 'vite', 'remix']) +}); + +// Message schema +export const chatMessageSchema = z.object({ + message: z.string().min(1).max(5000), + conversationId: z.string().uuid() +}); + +// Validate function +export function validate(schema: z.Schema, data: unknown): T { + try { + return schema.parse(data); + } catch (error) { + if (error instanceof z.ZodError) { + const errors = error.errors.map((e) => e.message).join(', '); + throw new Error(`Validation failed: ${errors}`); + } + throw error; + } +} +``` + +## 4. SQL Injection Prevention + +Already handled by Supabase RLS, but for raw queries: + +**File: `src/lib/supabase/safe-query.ts`** + +```typescript +import { createClient } from './server'; + +export async function safeQuery( + query: string, + params: any[] = [] +): Promise { + const supabase = await createClient(); + + // Use parameterized queries + const { data, error } = await supabase.rpc('execute_safe_query', { + query_string: query, + query_params: params + }); + + if (error) throw error; + return data; +} + +// Example usage: +// const users = await safeQuery( +// 'SELECT * FROM users WHERE email = $1', +// ['user@example.com'] +// ); +``` + +--- + +# ⚑ IV. PERFORMANCE OPTIMIZATION + +## 1. Image Optimization + +**File: `src/components/optimized-image.tsx`** + +```typescript +import Image from 'next/image'; +import { useState } from 'react'; + +interface OptimizedImageProps { + src: string; + alt: string; + width?: number; + height?: number; + className?: string; + priority?: boolean; +} + +export function OptimizedImage({ + src, + alt, + width, + height, + className, + priority = false +}: OptimizedImageProps) { + const [isLoading, setIsLoading] = useState(true); + + return ( +
+ {alt} setIsLoading(false)} + className={` + duration-700 ease-in-out + ${isLoading ? 'scale-110 blur-2xl grayscale' : 'scale-100 blur-0 grayscale-0'} + `} + sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" + /> +
+ ); +} +``` + +## 2. Code Splitting + +**File: `src/app/project/[id]/page.tsx`** + +```typescript +import dynamic from 'next/dynamic'; +import { Suspense } from 'react'; +import { LoadingSpinner } from '@/components/ui/loading'; + +// Lazy load heavy components +const ProjectEditor = dynamic( + () => import('@/components/project/project-editor'), + { + loading: () => , + ssr: false + } +); + +const LivePreview = dynamic( + () => import('@/components/preview/live-preview'), + { + loading: () => , + ssr: false + } +); + +export default async function ProjectPage({ params }: { params: { id: string } }) { + // ... fetch data + + return ( + }> + + + ); +} +``` + +## 3. Database Query Optimization + +**File: `src/lib/supabase/optimized-queries.ts`** + +```typescript +import { createClient } from './server'; + +// Use select to only fetch needed columns +export async function getProjects() { + const supabase = await createClient(); + + const { data, error } = await supabase + .from('projects') + .select('id, name, description, updated_at') // Only needed fields + .order('updated_at', { ascending: false }) + .limit(20); // Pagination + + if (error) throw error; + return data; +} + +// Use joins to avoid N+1 queries +export async function getProjectWithMessages(projectId: string) { + const supabase = await createClient(); + + const { data, error } = await supabase + .from('projects') + .select(` + *, + conversations ( + id, + messages ( + id, + role, + content, + created_at + ) + ) + `) + .eq('id', projectId) + .single(); + + if (error) throw error; + return data; +} + +// Use count for pagination +export async function getProjectsWithCount(page: number = 1, limit: number = 20) { + const supabase = await createClient(); + + const from = (page - 1) * limit; + const to = from + limit - 1; + + const { data, error, count } = await supabase + .from('projects') + .select('*', { count: 'exact' }) + .range(from, to) + .order('updated_at', { ascending: false }); + + if (error) throw error; + + return { + projects: data, + total: count, + page, + totalPages: Math.ceil((count || 0) / limit) + }; +} +``` + +--- + +_Continue vα»›i Production Deployment vΓ  Disaster Recovery trong message tiαΊΏp..._