Files
daniel 41cbbea00c perf(dashboard): reducir payload del home 87% y restaurar caching en Cloudflare
- Split components.json: índice sin content (1.9 MB / 249 KB br vs 15.6 MB / 4.15 MB br).
  Generador produce component-content/{type}/{slug}.json por componente y auto-copia
  a dashboard/public/. Página de detalle carga contenido on-demand via fetchComponentContent().
- Restaurar cabeceras de caché y seguridad perdidas en la migración Vercel→Cloudflare:
  _headers con Cache-Control max-age=86400 para JSONs, immutable para _astro/*,
  y HSTS, X-Frame-Options, Referrer-Policy, Permissions-Policy en todas las rutas.
- Eliminar dashboard/vercel.json (config muerta desde migración a Cloudflare Pages).
- Diferir hidratación: ClerkIsland y AuthButton client:load → client:idle;
  SaveToCollectionButton → client:visible.
- Eliminar gpt-tokenizer del bundle del navegador: reemplazado por estimación chars/4.
- Actualizar .claude/rules/dashboard.md para reflejar hosting en Cloudflare Pages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 16:58:13 -04:00

1 line
21 KiB
JSON

{"content": "---\nallowed-tools: Read, Write, Edit\nargument-hint: [function-name] [--auth] [--geo] [--transform] [--proxy]\ndescription: Generate optimized Vercel Edge Functions with geolocation, authentication, and data transformation\n---\n\n## Vercel Edge Function Generator\n\n**Function Name**: $ARGUMENTS\n\n## Current Project Analysis\n\n### Project Structure\n- Vercel config: @vercel.json (if exists)\n- Next.js config: @next.config.js\n- API routes: @app/api/ or @pages/api/\n- Middleware: @middleware.ts (if exists)\n\n### Framework Detection\n- Package.json: @package.json\n- TypeScript config: @tsconfig.json (if exists)\n- Environment variables: @.env.local (if exists)\n\n## Edge Function Implementation Strategy\n\n### 1. File Structure Creation\nGenerate comprehensive edge function structure:\n```\napi/edge/[function-name]/\n├── index.ts # Main edge function\n├── types.ts # TypeScript types\n├── utils.ts # Utility functions\n├── config.ts # Configuration\n└── __tests__/\n └── [function-name].test.ts # Unit tests\n```\n\n### 2. Base Edge Function Template\n```typescript\n// api/edge/[function-name]/index.ts\nimport { NextRequest, NextResponse } from 'next/server';\n\nexport const runtime = 'edge';\n\nexport async function GET(request: NextRequest) {\n try {\n // Get geolocation data\n const country = request.geo?.country || 'Unknown';\n const city = request.geo?.city || 'Unknown';\n const region = request.geo?.region || 'Unknown';\n \n // Get request metadata\n const ip = request.headers.get('x-forwarded-for') || 'Unknown';\n const userAgent = request.headers.get('user-agent') || 'Unknown';\n const referer = request.headers.get('referer') || 'Unknown';\n \n // Process request\n const result = await processRequest({\n geo: { country, city, region },\n ip,\n userAgent,\n referer,\n url: request.url,\n });\n \n return NextResponse.json(result, {\n status: 200,\n headers: {\n 'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300',\n 'Content-Type': 'application/json',\n 'X-Edge-Location': region,\n },\n });\n \n } catch (error) {\n console.error('Edge function error:', error);\n \n return NextResponse.json(\n { error: 'Internal server error' },\n { status: 500 }\n );\n }\n}\n\nexport async function POST(request: NextRequest) {\n try {\n const body = await request.json();\n \n // Validate request body\n const validationResult = validateRequestBody(body);\n if (!validationResult.valid) {\n return NextResponse.json(\n { error: 'Invalid request body', details: validationResult.errors },\n { status: 400 }\n );\n }\n \n // Process POST request\n const result = await processPostRequest(body, request);\n \n return NextResponse.json(result, {\n status: 201,\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n \n } catch (error) {\n console.error('Edge function POST error:', error);\n \n return NextResponse.json(\n { error: 'Internal server error' },\n { status: 500 }\n );\n }\n}\n\nasync function processRequest(metadata: RequestMetadata): Promise<any> {\n // Implement your edge function logic here\n return {\n message: 'Edge function executed successfully',\n metadata,\n timestamp: new Date().toISOString(),\n };\n}\n\nasync function processPostRequest(body: any, request: NextRequest): Promise<any> {\n // Implement POST logic here\n return {\n message: 'POST processed successfully',\n data: body,\n timestamp: new Date().toISOString(),\n };\n}\n\nfunction validateRequestBody(body: any): ValidationResult {\n // Implement validation logic\n return { valid: true, errors: [] };\n}\n\ninterface RequestMetadata {\n geo: {\n country: string;\n city: string;\n region: string;\n };\n ip: string;\n userAgent: string;\n referer: string;\n url: string;\n}\n\ninterface ValidationResult {\n valid: boolean;\n errors: string[];\n}\n```\n\n## Specialized Edge Function Types\n\n### 1. Geolocation-Based Content Delivery\n```typescript\n// api/edge/geo-content/index.ts\nimport { NextRequest, NextResponse } from 'next/server';\n\nexport const runtime = 'edge';\n\ninterface ContentConfig {\n [country: string]: {\n currency: string;\n language: string;\n content: string;\n pricing: number;\n };\n}\n\nconst contentConfig: ContentConfig = {\n 'US': {\n currency: 'USD',\n language: 'en-US',\n content: 'Welcome to our US store!',\n pricing: 99.99,\n },\n 'GB': {\n currency: 'GBP',\n language: 'en-GB',\n content: 'Welcome to our UK store!',\n pricing: 79.99,\n },\n 'DE': {\n currency: 'EUR',\n language: 'de-DE',\n content: 'Willkommen in unserem deutschen Shop!',\n pricing: 89.99,\n },\n};\n\nexport async function GET(request: NextRequest) {\n const country = request.geo?.country || 'US';\n const config = contentConfig[country] || contentConfig['US'];\n \n // Add region-specific headers\n const response = NextResponse.json({\n country,\n ...config,\n edgeLocation: request.geo?.region,\n timestamp: new Date().toISOString(),\n });\n \n response.headers.set('Cache-Control', 'public, s-maxage=3600, stale-while-revalidate=86400');\n response.headers.set('Vary', 'Accept-Language, CloudFront-Viewer-Country');\n response.headers.set('Content-Language', config.language);\n \n return response;\n}\n```\n\n### 2. Authentication Edge Function\n```typescript\n// api/edge/auth-check/index.ts\nimport { NextRequest, NextResponse } from 'next/server';\nimport { jwtVerify } from 'jose';\n\nexport const runtime = 'edge';\n\nconst JWT_SECRET = new TextEncoder().encode(\n process.env.JWT_SECRET || 'your-secret-key'\n);\n\nexport async function GET(request: NextRequest) {\n try {\n // Extract token from header or cookie\n const authHeader = request.headers.get('authorization');\n const cookieToken = request.cookies.get('auth-token')?.value;\n \n const token = authHeader?.replace('Bearer ', '') || cookieToken;\n \n if (!token) {\n return NextResponse.json(\n { error: 'No token provided', authenticated: false },\n { status: 401 }\n );\n }\n \n // Verify JWT token\n const { payload } = await jwtVerify(token, JWT_SECRET);\n \n // Return user info\n return NextResponse.json({\n authenticated: true,\n user: {\n id: payload.sub,\n email: payload.email,\n role: payload.role,\n exp: payload.exp,\n },\n location: {\n country: request.geo?.country,\n city: request.geo?.city,\n },\n });\n \n } catch (error) {\n console.error('Auth verification failed:', error);\n \n return NextResponse.json(\n { error: 'Invalid token', authenticated: false },\n { status: 401 }\n );\n }\n}\n\nexport async function POST(request: NextRequest) {\n try {\n const { username, password } = await request.json();\n \n // Validate credentials (implement your logic)\n const user = await validateCredentials(username, password);\n \n if (!user) {\n return NextResponse.json(\n { error: 'Invalid credentials' },\n { status: 401 }\n );\n }\n \n // Generate JWT token\n const token = await generateJWT(user);\n \n const response = NextResponse.json({\n success: true,\n user: {\n id: user.id,\n email: user.email,\n role: user.role,\n },\n });\n \n // Set secure cookie\n response.cookies.set('auth-token', token, {\n httpOnly: true,\n secure: process.env.NODE_ENV === 'production',\n sameSite: 'strict',\n maxAge: 24 * 60 * 60, // 24 hours\n });\n \n return response;\n \n } catch (error) {\n console.error('Authentication error:', error);\n \n return NextResponse.json(\n { error: 'Authentication failed' },\n { status: 500 }\n );\n }\n}\n\nasync function validateCredentials(username: string, password: string) {\n // Implement credential validation\n // This would typically involve database lookup\n return null; // Placeholder\n}\n\nasync function generateJWT(user: any): Promise<string> {\n // Implement JWT generation\n // This would use a proper JWT library\n return 'jwt-token'; // Placeholder\n}\n```\n\n### 3. Data Transformation Edge Function\n```typescript\n// api/edge/transform/index.ts\nimport { NextRequest, NextResponse } from 'next/server';\n\nexport const runtime = 'edge';\n\ninterface TransformConfig {\n format: 'json' | 'xml' | 'csv';\n fields?: string[];\n transforms?: Record<string, (value: any) => any>;\n}\n\nconst transformers = {\n // Currency conversion\n currency: (value: number, targetCurrency: string = 'USD') => {\n const rates = { USD: 1, EUR: 0.85, GBP: 0.73 };\n return value * (rates[targetCurrency as keyof typeof rates] || 1);\n },\n \n // Date formatting\n date: (value: string, format: string = 'ISO') => {\n const date = new Date(value);\n if (format === 'ISO') return date.toISOString();\n if (format === 'US') return date.toLocaleDateString('en-US');\n return date.toString();\n },\n \n // Text formatting\n text: (value: string, caseType: string = 'lower') => {\n if (caseType === 'upper') return value.toUpperCase();\n if (caseType === 'title') return value.replace(/\\w\\S*/g, txt => \n txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()\n );\n return value.toLowerCase();\n },\n};\n\nexport async function POST(request: NextRequest) {\n try {\n const { data, config }: { data: any; config: TransformConfig } = await request.json();\n \n if (!data || !config) {\n return NextResponse.json(\n { error: 'Missing data or config' },\n { status: 400 }\n );\n }\n \n // Apply transformations\n const transformedData = await transformData(data, config, request);\n \n // Format output based on requested format\n const output = await formatOutput(transformedData, config.format);\n \n const response = new NextResponse(output, {\n status: 200,\n headers: {\n 'Content-Type': getContentType(config.format),\n 'Cache-Control': 'public, s-maxage=300',\n },\n });\n \n return response;\n \n } catch (error) {\n console.error('Transform error:', error);\n \n return NextResponse.json(\n { error: 'Transformation failed' },\n { status: 500 }\n );\n }\n}\n\nasync function transformData(data: any, config: TransformConfig, request: NextRequest) {\n const country = request.geo?.country || 'US';\n \n // Apply field filtering if specified\n if (config.fields && Array.isArray(data)) {\n data = data.map(item => {\n const filtered: any = {};\n config.fields!.forEach(field => {\n if (item.hasOwnProperty(field)) {\n filtered[field] = item[field];\n }\n });\n return filtered;\n });\n }\n \n // Apply custom transforms\n if (config.transforms) {\n Object.entries(config.transforms).forEach(([field, transformFunc]) => {\n if (Array.isArray(data)) {\n data = data.map(item => ({\n ...item,\n [field]: transformFunc(item[field]),\n }));\n } else if (data.hasOwnProperty(field)) {\n data[field] = transformFunc(data[field]);\n }\n });\n }\n \n // Add geo context\n return {\n ...data,\n _meta: {\n transformedAt: new Date().toISOString(),\n location: country,\n edgeRegion: request.geo?.region,\n },\n };\n}\n\nasync function formatOutput(data: any, format: string): Promise<string> {\n switch (format) {\n case 'xml':\n return jsonToXml(data);\n case 'csv':\n return jsonToCsv(data);\n case 'json':\n default:\n return JSON.stringify(data, null, 2);\n }\n}\n\nfunction getContentType(format: string): string {\n switch (format) {\n case 'xml': return 'application/xml';\n case 'csv': return 'text/csv';\n case 'json':\n default: return 'application/json';\n }\n}\n\nfunction jsonToXml(data: any): string {\n // Simple XML conversion (implement proper XML library for production)\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?><root>${JSON.stringify(data)}</root>`;\n}\n\nfunction jsonToCsv(data: any): string {\n // Simple CSV conversion (implement proper CSV library for production)\n if (Array.isArray(data) && data.length > 0) {\n const headers = Object.keys(data[0]);\n const rows = data.map(row => headers.map(header => row[header] || '').join(','));\n return [headers.join(','), ...rows].join('\\n');\n }\n return '';\n}\n```\n\n### 4. Proxy and Cache Edge Function\n```typescript\n// api/edge/proxy/index.ts\nimport { NextRequest, NextResponse } from 'next/server';\n\nexport const runtime = 'edge';\n\ninterface ProxyConfig {\n targetUrl: string;\n cacheTime: number;\n headers?: Record<string, string>;\n transformResponse?: boolean;\n}\n\nconst proxyConfigs: Record<string, ProxyConfig> = {\n 'api': {\n targetUrl: 'https://jsonplaceholder.typicode.com',\n cacheTime: 300, // 5 minutes\n headers: {\n 'User-Agent': 'Vercel-Edge-Proxy/1.0',\n },\n },\n 'cdn': {\n targetUrl: 'https://cdn.example.com',\n cacheTime: 3600, // 1 hour\n transformResponse: false,\n },\n};\n\nexport async function GET(request: NextRequest) {\n try {\n const url = new URL(request.url);\n const proxyType = url.searchParams.get('type') || 'api';\n const targetPath = url.searchParams.get('path') || '';\n \n const config = proxyConfigs[proxyType];\n if (!config) {\n return NextResponse.json(\n { error: 'Invalid proxy type' },\n { status: 400 }\n );\n }\n \n // Build target URL\n const targetUrl = `${config.targetUrl}${targetPath}`;\n \n // Check cache first (simplified - use proper cache in production)\n const cacheKey = `proxy:${targetUrl}`;\n \n // Make request to target\n const response = await fetch(targetUrl, {\n headers: {\n ...config.headers,\n 'X-Forwarded-For': request.headers.get('x-forwarded-for') || '',\n 'X-Real-IP': request.headers.get('x-real-ip') || '',\n },\n });\n \n if (!response.ok) {\n return NextResponse.json(\n { error: 'Upstream server error' },\n { status: response.status }\n );\n }\n \n let data;\n const contentType = response.headers.get('content-type') || '';\n \n if (contentType.includes('application/json')) {\n data = await response.json();\n \n // Transform response if configured\n if (config.transformResponse) {\n data = await transformProxyResponse(data, request);\n }\n \n return NextResponse.json(data, {\n status: 200,\n headers: {\n 'Cache-Control': `public, s-maxage=${config.cacheTime}, stale-while-revalidate=${config.cacheTime * 2}`,\n 'X-Proxy-Cache': 'MISS',\n 'X-Edge-Location': request.geo?.region || 'unknown',\n },\n });\n } else {\n // For non-JSON responses, pass through\n const blob = await response.blob();\n \n return new NextResponse(blob, {\n status: 200,\n headers: {\n 'Content-Type': contentType,\n 'Cache-Control': `public, s-maxage=${config.cacheTime}`,\n },\n });\n }\n \n } catch (error) {\n console.error('Proxy error:', error);\n \n return NextResponse.json(\n { error: 'Proxy request failed' },\n { status: 502 }\n );\n }\n}\n\nasync function transformProxyResponse(data: any, request: NextRequest) {\n // Add geo context to proxied data\n return {\n ...data,\n _proxy: {\n timestamp: new Date().toISOString(),\n location: request.geo?.country,\n region: request.geo?.region,\n },\n };\n}\n```\n\n## Edge Function Utilities\n\n### 1. Configuration Management\n```typescript\n// api/edge/[function-name]/config.ts\nexport interface EdgeFunctionConfig {\n cacheTime: number;\n rateLimit: {\n requests: number;\n windowMs: number;\n };\n geo: {\n enabled: boolean;\n restrictedCountries?: string[];\n };\n security: {\n corsOrigins: string[];\n requireAuth: boolean;\n };\n}\n\nexport const defaultConfig: EdgeFunctionConfig = {\n cacheTime: 300, // 5 minutes\n rateLimit: {\n requests: 100,\n windowMs: 60000, // 1 minute\n },\n geo: {\n enabled: true,\n },\n security: {\n corsOrigins: ['*'],\n requireAuth: false,\n },\n};\n```\n\n### 2. Utility Functions\n```typescript\n// api/edge/[function-name]/utils.ts\nexport function getClientIP(request: NextRequest): string {\n return request.headers.get('x-forwarded-for') ||\n request.headers.get('x-real-ip') ||\n request.ip ||\n 'unknown';\n}\n\nexport function generateCacheKey(request: NextRequest, suffix?: string): string {\n const url = new URL(request.url);\n const baseKey = `${url.pathname}${url.search}`;\n return suffix ? `${baseKey}:${suffix}` : baseKey;\n}\n\nexport function createCorsResponse(\n data: any,\n origins: string[] = ['*']\n): NextResponse {\n const response = NextResponse.json(data);\n \n response.headers.set('Access-Control-Allow-Origin', origins.join(', '));\n response.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');\n \n return response;\n}\n\nexport function validateGeoRestrictions(\n request: NextRequest,\n restrictedCountries: string[] = []\n): boolean {\n const country = request.geo?.country;\n return !country || !restrictedCountries.includes(country);\n}\n```\n\n### 3. Testing Framework\n```typescript\n// api/edge/[function-name]/__tests__/[function-name].test.ts\nimport { NextRequest } from 'next/server';\nimport { GET, POST } from '../index';\n\n// Mock geo data\nconst createMockRequest = (url: string, options: any = {}) => {\n const request = new NextRequest(url, options);\n \n // Mock geo property\n Object.defineProperty(request, 'geo', {\n value: {\n country: 'US',\n city: 'New York',\n region: 'us-east-1',\n },\n });\n \n return request;\n};\n\ndescribe('Edge Function', () => {\n describe('GET requests', () => {\n it('should return geo-based content', async () => {\n const request = createMockRequest('http://localhost:3000/api/edge/test');\n const response = await GET(request);\n const data = await response.json();\n \n expect(response.status).toBe(200);\n expect(data.metadata.geo.country).toBe('US');\n });\n \n it('should handle missing geo data', async () => {\n const request = new NextRequest('http://localhost:3000/api/edge/test');\n const response = await GET(request);\n const data = await response.json();\n \n expect(response.status).toBe(200);\n expect(data.metadata.geo.country).toBe('Unknown');\n });\n });\n \n describe('POST requests', () => {\n it('should validate request body', async () => {\n const request = createMockRequest('http://localhost:3000/api/edge/test', {\n method: 'POST',\n body: JSON.stringify({ invalid: 'data' }),\n headers: { 'Content-Type': 'application/json' },\n });\n \n const response = await POST(request);\n const data = await response.json();\n \n expect(response.status).toBe(400);\n expect(data.error).toBe('Invalid request body');\n });\n });\n});\n```\n\n## Performance and Optimization\n\n### 1. Response Optimization\n```typescript\n// Optimize responses for edge performance\nexport function optimizeResponse(data: any, request: NextRequest): NextResponse {\n const response = NextResponse.json(data);\n \n // Set appropriate cache headers\n const cacheTime = getCacheTime(request.url);\n response.headers.set(\n 'Cache-Control',\n `public, s-maxage=${cacheTime}, stale-while-revalidate=${cacheTime * 2}`\n );\n \n // Add compression hints\n response.headers.set('Content-Encoding', 'gzip');\n \n // Add performance headers\n response.headers.set('X-Edge-Location', request.geo?.region || 'unknown');\n \n return response;\n}\n\nfunction getCacheTime(url: string): number {\n // Dynamic cache time based on URL patterns\n if (url.includes('/static/')) return 3600; // 1 hour\n if (url.includes('/api/')) return 60; // 1 minute\n return 300; // 5 minutes default\n}\n```\n\n### 2. Error Handling\n```typescript\nexport function createErrorResponse(\n error: unknown,\n request: NextRequest\n): NextResponse {\n console.error('Edge function error:', error);\n \n // Log error with context\n const errorContext = {\n url: request.url,\n method: request.method,\n country: request.geo?.country,\n timestamp: new Date().toISOString(),\n };\n \n // Return appropriate error response\n return NextResponse.json(\n {\n error: 'Internal server error',\n requestId: generateRequestId(),\n },\n {\n status: 500,\n headers: {\n 'X-Error-Context': JSON.stringify(errorContext),\n },\n }\n );\n}\n\nfunction generateRequestId(): string {\n return Math.random().toString(36).substr(2, 9);\n}\n```\n\nGenerate comprehensive edge function implementation with the requested features, proper TypeScript types, error handling, and optimization patterns."}