Back to Commands

Vercel Edge Function

Commands nextjs-vercel 255
Install Command
npx claude-code-templates@latest --command nextjs-vercel/vercel-edge-function
View on GitHub

Content

Vercel Edge Function Generator

Function Name: $ARGUMENTS

Current Project Analysis

Project Structure

  • Vercel config: @vercel.json (if exists)
  • Next.js config: @next.config.js
  • API routes: @app/api/ or @pages/api/
  • Middleware: @middleware.ts (if exists)

Framework Detection

  • Package.json: @package.json
  • TypeScript config: @tsconfig.json (if exists)
  • Environment variables: @.env.local (if exists)

Edge Function Implementation Strategy

1. File Structure Creation

Generate comprehensive edge function structure:

api/edge/[function-name]/
├── index.ts                    # Main edge function
├── types.ts                   # TypeScript types
├── utils.ts                   # Utility functions
├── config.ts                  # Configuration
└── __tests__/
    └── [function-name].test.ts # Unit tests

2. Base Edge Function Template

typescript
// api/edge/[function-name]/index.ts
import { NextRequest, NextResponse } from 'next/server';

export const runtime = 'edge';

export async function GET(request: NextRequest) {
  try {
    // Get geolocation data
    const country = request.geo?.country || 'Unknown';
    const city = request.geo?.city || 'Unknown';
    const region = request.geo?.region || 'Unknown';
    
    // Get request metadata
    const ip = request.headers.get('x-forwarded-for') || 'Unknown';
    const userAgent = request.headers.get('user-agent') || 'Unknown';
    const referer = request.headers.get('referer') || 'Unknown';
    
    // Process request
    const result = await processRequest({
      geo: { country, city, region },
      ip,
      userAgent,
      referer,
      url: request.url,
    });
    
    return NextResponse.json(result, {
      status: 200,
      headers: {
        'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300',
        'Content-Type': 'application/json',
        'X-Edge-Location': region,
      },
    });
    
  } catch (error) {
    console.error('Edge function error:', error);
    
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    );
  }
}

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    
    // Validate request body
    const validationResult = validateRequestBody(body);
    if (!validationResult.valid) {
      return NextResponse.json(
        { error: 'Invalid request body', details: validationResult.errors },
        { status: 400 }
      );
    }
    
    // Process POST request
    const result = await processPostRequest(body, request);
    
    return NextResponse.json(result, {
      status: 201,
      headers: {
        'Content-Type': 'application/json',
      },
    });
    
  } catch (error) {
    console.error('Edge function POST error:', error);
    
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    );
  }
}

async function processRequest(metadata: RequestMetadata): Promise<any> {
  // Implement your edge function logic here
  return {
    message: 'Edge function executed successfully',
    metadata,
    timestamp: new Date().toISOString(),
  };
}

async function processPostRequest(body: any, request: NextRequest): Promise<any> {
  // Implement POST logic here
  return {
    message: 'POST processed successfully',
    data: body,
    timestamp: new Date().toISOString(),
  };
}

function validateRequestBody(body: any): ValidationResult {
  // Implement validation logic
  return { valid: true, errors: [] };
}

interface RequestMetadata {
  geo: {
    country: string;
    city: string;
    region: string;
  };
  ip: string;
  userAgent: string;
  referer: string;
  url: string;
}

interface ValidationResult {
  valid: boolean;
  errors: string[];
}

Specialized Edge Function Types

1. Geolocation-Based Content Delivery

typescript
// api/edge/geo-content/index.ts
import { NextRequest, NextResponse } from 'next/server';

export const runtime = 'edge';

interface ContentConfig {
  [country: string]: {
    currency: string;
    language: string;
    content: string;
    pricing: number;
  };
}

const contentConfig: ContentConfig = {
  'US': {
    currency: 'USD',
    language: 'en-US',
    content: 'Welcome to our US store!',
    pricing: 99.99,
  },
  'GB': {
    currency: 'GBP',
    language: 'en-GB',
    content: 'Welcome to our UK store!',
    pricing: 79.99,
  },
  'DE': {
    currency: 'EUR',
    language: 'de-DE',
    content: 'Willkommen in unserem deutschen Shop!',
    pricing: 89.99,
  },
};

export async function GET(request: NextRequest) {
  const country = request.geo?.country || 'US';
  const config = contentConfig[country] || contentConfig['US'];
  
  // Add region-specific headers
  const response = NextResponse.json({
    country,
    ...config,
    edgeLocation: request.geo?.region,
    timestamp: new Date().toISOString(),
  });
  
  response.headers.set('Cache-Control', 'public, s-maxage=3600, stale-while-revalidate=86400');
  response.headers.set('Vary', 'Accept-Language, CloudFront-Viewer-Country');
  response.headers.set('Content-Language', config.language);
  
  return response;
}

2. Authentication Edge Function

typescript
// api/edge/auth-check/index.ts
import { NextRequest, NextResponse } from 'next/server';
import { jwtVerify } from 'jose';

export const runtime = 'edge';

const JWT_SECRET = new TextEncoder().encode(
  process.env.JWT_SECRET || 'your-secret-key'
);

export async function GET(request: NextRequest) {
  try {
    // Extract token from header or cookie
    const authHeader = request.headers.get('authorization');
    const cookieToken = request.cookies.get('auth-token')?.value;
    
    const token = authHeader?.replace('Bearer ', '') || cookieToken;
    
    if (!token) {
      return NextResponse.json(
        { error: 'No token provided', authenticated: false },
        { status: 401 }
      );
    }
    
    // Verify JWT token
    const { payload } = await jwtVerify(token, JWT_SECRET);
    
    // Return user info
    return NextResponse.json({
      authenticated: true,
      user: {
        id: payload.sub,
        email: payload.email,
        role: payload.role,
        exp: payload.exp,
      },
      location: {
        country: request.geo?.country,
        city: request.geo?.city,
      },
    });
    
  } catch (error) {
    console.error('Auth verification failed:', error);
    
    return NextResponse.json(
      { error: 'Invalid token', authenticated: false },
      { status: 401 }
    );
  }
}

export async function POST(request: NextRequest) {
  try {
    const { username, password } = await request.json();
    
    // Validate credentials (implement your logic)
    const user = await validateCredentials(username, password);
    
    if (!user) {
      return NextResponse.json(
        { error: 'Invalid credentials' },
        { status: 401 }
      );
    }
    
    // Generate JWT token
    const token = await generateJWT(user);
    
    const response = NextResponse.json({
      success: true,
      user: {
        id: user.id,
        email: user.email,
        role: user.role,
      },
    });
    
    // Set secure cookie
    response.cookies.set('auth-token', token, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'strict',
      maxAge: 24 * 60 * 60, // 24 hours
    });
    
    return response;
    
  } catch (error) {
    console.error('Authentication error:', error);
    
    return NextResponse.json(
      { error: 'Authentication failed' },
      { status: 500 }
    );
  }
}

async function validateCredentials(username: string, password: string) {
  // Implement credential validation
  // This would typically involve database lookup
  return null; // Placeholder
}

async function generateJWT(user: any): Promise<string> {
  // Implement JWT generation
  // This would use a proper JWT library
  return 'jwt-token'; // Placeholder
}

3. Data Transformation Edge Function

typescript
// api/edge/transform/index.ts
import { NextRequest, NextResponse } from 'next/server';

export const runtime = 'edge';

interface TransformConfig {
  format: 'json' | 'xml' | 'csv';
  fields?: string[];
  transforms?: Record<string, (value: any) => any>;
}

const transformers = {
  // Currency conversion
  currency: (value: number, targetCurrency: string = 'USD') => {
    const rates = { USD: 1, EUR: 0.85, GBP: 0.73 };
    return value * (rates[targetCurrency as keyof typeof rates] || 1);
  },
  
  // Date formatting
  date: (value: string, format: string = 'ISO') => {
    const date = new Date(value);
    if (format === 'ISO') return date.toISOString();
    if (format === 'US') return date.toLocaleDateString('en-US');
    return date.toString();
  },
  
  // Text formatting
  text: (value: string, caseType: string = 'lower') => {
    if (caseType === 'upper') return value.toUpperCase();
    if (caseType === 'title') return value.replace(/\w\S*/g, txt => 
      txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
    );
    return value.toLowerCase();
  },
};

export async function POST(request: NextRequest) {
  try {
    const { data, config }: { data: any; config: TransformConfig } = await request.json();
    
    if (!data || !config) {
      return NextResponse.json(
        { error: 'Missing data or config' },
        { status: 400 }
      );
    }
    
    // Apply transformations
    const transformedData = await transformData(data, config, request);
    
    // Format output based on requested format
    const output = await formatOutput(transformedData, config.format);
    
    const response = new NextResponse(output, {
      status: 200,
      headers: {
        'Content-Type': getContentType(config.format),
        'Cache-Control': 'public, s-maxage=300',
      },
    });
    
    return response;
    
  } catch (error) {
    console.error('Transform error:', error);
    
    return NextResponse.json(
      { error: 'Transformation failed' },
      { status: 500 }
    );
  }
}

async function transformData(data: any, config: TransformConfig, request: NextRequest) {
  const country = request.geo?.country || 'US';
  
  // Apply field filtering if specified
  if (config.fields && Array.isArray(data)) {
    data = data.map(item => {
      const filtered: any = {};
      config.fields!.forEach(field => {
        if (item.hasOwnProperty(field)) {
          filtered[field] = item[field];
        }
      });
      return filtered;
    });
  }
  
  // Apply custom transforms
  if (config.transforms) {
    Object.entries(config.transforms).forEach(([field, transformFunc]) => {
      if (Array.isArray(data)) {
        data = data.map(item => ({
          ...item,
          [field]: transformFunc(item[field]),
        }));
      } else if (data.hasOwnProperty(field)) {
        data[field] = transformFunc(data[field]);
      }
    });
  }
  
  // Add geo context
  return {
    ...data,
    _meta: {
      transformedAt: new Date().toISOString(),
      location: country,
      edgeRegion: request.geo?.region,
    },
  };
}

async function formatOutput(data: any, format: string): Promise<string> {
  switch (format) {
    case 'xml':
      return jsonToXml(data);
    case 'csv':
      return jsonToCsv(data);
    case 'json':
    default:
      return JSON.stringify(data, null, 2);
  }
}

function getContentType(format: string): string {
  switch (format) {
    case 'xml': return 'application/xml';
    case 'csv': return 'text/csv';
    case 'json':
    default: return 'application/json';
  }
}

function jsonToXml(data: any): string {
  // Simple XML conversion (implement proper XML library for production)
  return `<?xml version="1.0" encoding="UTF-8"?><root>${JSON.stringify(data)}</root>`;
}

function jsonToCsv(data: any): string {
  // Simple CSV conversion (implement proper CSV library for production)
  if (Array.isArray(data) && data.length > 0) {
    const headers = Object.keys(data[0]);
    const rows = data.map(row => headers.map(header => row[header] || '').join(','));
    return [headers.join(','), ...rows].join('\n');
  }
  return '';
}

4. Proxy and Cache Edge Function

typescript
// api/edge/proxy/index.ts
import { NextRequest, NextResponse } from 'next/server';

export const runtime = 'edge';

interface ProxyConfig {
  targetUrl: string;
  cacheTime: number;
  headers?: Record<string, string>;
  transformResponse?: boolean;
}

const proxyConfigs: Record<string, ProxyConfig> = {
  'api': {
    targetUrl: 'https://jsonplaceholder.typicode.com',
    cacheTime: 300, // 5 minutes
    headers: {
      'User-Agent': 'Vercel-Edge-Proxy/1.0',
    },
  },
  'cdn': {
    targetUrl: 'https://cdn.example.com',
    cacheTime: 3600, // 1 hour
    transformResponse: false,
  },
};

export async function GET(request: NextRequest) {
  try {
    const url = new URL(request.url);
    const proxyType = url.searchParams.get('type') || 'api';
    const targetPath = url.searchParams.get('path') || '';
    
    const config = proxyConfigs[proxyType];
    if (!config) {
      return NextResponse.json(
        { error: 'Invalid proxy type' },
        { status: 400 }
      );
    }
    
    // Build target URL
    const targetUrl = `${config.targetUrl}${targetPath}`;
    
    // Check cache first (simplified - use proper cache in production)
    const cacheKey = `proxy:${targetUrl}`;
    
    // Make request to target
    const response = await fetch(targetUrl, {
      headers: {
        ...config.headers,
        'X-Forwarded-For': request.headers.get('x-forwarded-for') || '',
        'X-Real-IP': request.headers.get('x-real-ip') || '',
      },
    });
    
    if (!response.ok) {
      return NextResponse.json(
        { error: 'Upstream server error' },
        { status: response.status }
      );
    }
    
    let data;
    const contentType = response.headers.get('content-type') || '';
    
    if (contentType.includes('application/json')) {
      data = await response.json();
      
      // Transform response if configured
      if (config.transformResponse) {
        data = await transformProxyResponse(data, request);
      }
      
      return NextResponse.json(data, {
        status: 200,
        headers: {
          'Cache-Control': `public, s-maxage=${config.cacheTime}, stale-while-revalidate=${config.cacheTime * 2}`,
          'X-Proxy-Cache': 'MISS',
          'X-Edge-Location': request.geo?.region || 'unknown',
        },
      });
    } else {
      // For non-JSON responses, pass through
      const blob = await response.blob();
      
      return new NextResponse(blob, {
        status: 200,
        headers: {
          'Content-Type': contentType,
          'Cache-Control': `public, s-maxage=${config.cacheTime}`,
        },
      });
    }
    
  } catch (error) {
    console.error('Proxy error:', error);
    
    return NextResponse.json(
      { error: 'Proxy request failed' },
      { status: 502 }
    );
  }
}

async function transformProxyResponse(data: any, request: NextRequest) {
  // Add geo context to proxied data
  return {
    ...data,
    _proxy: {
      timestamp: new Date().toISOString(),
      location: request.geo?.country,
      region: request.geo?.region,
    },
  };
}

Edge Function Utilities

1. Configuration Management

typescript
// api/edge/[function-name]/config.ts
export interface EdgeFunctionConfig {
  cacheTime: number;
  rateLimit: {
    requests: number;
    windowMs: number;
  };
  geo: {
    enabled: boolean;
    restrictedCountries?: string[];
  };
  security: {
    corsOrigins: string[];
    requireAuth: boolean;
  };
}

export const defaultConfig: EdgeFunctionConfig = {
  cacheTime: 300, // 5 minutes
  rateLimit: {
    requests: 100,
    windowMs: 60000, // 1 minute
  },
  geo: {
    enabled: true,
  },
  security: {
    corsOrigins: ['*'],
    requireAuth: false,
  },
};

2. Utility Functions

typescript
// api/edge/[function-name]/utils.ts
export function getClientIP(request: NextRequest): string {
  return request.headers.get('x-forwarded-for') ||
    request.headers.get('x-real-ip') ||
    request.ip ||
    'unknown';
}

export function generateCacheKey(request: NextRequest, suffix?: string): string {
  const url = new URL(request.url);
  const baseKey = `${url.pathname}${url.search}`;
  return suffix ? `${baseKey}:${suffix}` : baseKey;
}

export function createCorsResponse(
  data: any,
  origins: string[] = ['*']
): NextResponse {
  const response = NextResponse.json(data);
  
  response.headers.set('Access-Control-Allow-Origin', origins.join(', '));
  response.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  
  return response;
}

export function validateGeoRestrictions(
  request: NextRequest,
  restrictedCountries: string[] = []
): boolean {
  const country = request.geo?.country;
  return !country || !restrictedCountries.includes(country);
}

3. Testing Framework

typescript
// api/edge/[function-name]/__tests__/[function-name].test.ts
import { NextRequest } from 'next/server';
import { GET, POST } from '../index';

// Mock geo data
const createMockRequest = (url: string, options: any = {}) => {
  const request = new NextRequest(url, options);
  
  // Mock geo property
  Object.defineProperty(request, 'geo', {
    value: {
      country: 'US',
      city: 'New York',
      region: 'us-east-1',
    },
  });
  
  return request;
};

describe('Edge Function', () => {
  describe('GET requests', () => {
    it('should return geo-based content', async () => {
      const request = createMockRequest('http://localhost:3000/api/edge/test');
      const response = await GET(request);
      const data = await response.json();
      
      expect(response.status).toBe(200);
      expect(data.metadata.geo.country).toBe('US');
    });
    
    it('should handle missing geo data', async () => {
      const request = new NextRequest('http://localhost:3000/api/edge/test');
      const response = await GET(request);
      const data = await response.json();
      
      expect(response.status).toBe(200);
      expect(data.metadata.geo.country).toBe('Unknown');
    });
  });
  
  describe('POST requests', () => {
    it('should validate request body', async () => {
      const request = createMockRequest('http://localhost:3000/api/edge/test', {
        method: 'POST',
        body: JSON.stringify({ invalid: 'data' }),
        headers: { 'Content-Type': 'application/json' },
      });
      
      const response = await POST(request);
      const data = await response.json();
      
      expect(response.status).toBe(400);
      expect(data.error).toBe('Invalid request body');
    });
  });
});

Performance and Optimization

1. Response Optimization

typescript
// Optimize responses for edge performance
export function optimizeResponse(data: any, request: NextRequest): NextResponse {
  const response = NextResponse.json(data);
  
  // Set appropriate cache headers
  const cacheTime = getCacheTime(request.url);
  response.headers.set(
    'Cache-Control',
    `public, s-maxage=${cacheTime}, stale-while-revalidate=${cacheTime * 2}`
  );
  
  // Add compression hints
  response.headers.set('Content-Encoding', 'gzip');
  
  // Add performance headers
  response.headers.set('X-Edge-Location', request.geo?.region || 'unknown');
  
  return response;
}

function getCacheTime(url: string): number {
  // Dynamic cache time based on URL patterns
  if (url.includes('/static/')) return 3600; // 1 hour
  if (url.includes('/api/')) return 60; // 1 minute
  return 300; // 5 minutes default
}

2. Error Handling

typescript
export function createErrorResponse(
  error: unknown,
  request: NextRequest
): NextResponse {
  console.error('Edge function error:', error);
  
  // Log error with context
  const errorContext = {
    url: request.url,
    method: request.method,
    country: request.geo?.country,
    timestamp: new Date().toISOString(),
  };
  
  // Return appropriate error response
  return NextResponse.json(
    {
      error: 'Internal server error',
      requestId: generateRequestId(),
    },
    {
      status: 500,
      headers: {
        'X-Error-Context': JSON.stringify(errorContext),
      },
    }
  );
}

function generateRequestId(): string {
  return Math.random().toString(36).substr(2, 9);
}

Generate comprehensive edge function implementation with the requested features, proper TypeScript types, error handling, and optimization patterns.

Stack Builder

0 components

Your stack is empty

Browse components and click the + button to add them to your stack for easy installation.