Skip to main content
command

vercel-edge-function — Claude Code command for Vercel Edge Functions

Generate a TypeScript starter for a Vercel Edge Function, with request handlers and examples for geolocation or JWT checks. Use it when adding an edge API route to a Next.js project.

Install into Claude Code · claude-code-templates All methods and platforms ↓
npx claude-code-templates@latest --command nextjs-vercel/vercel-edge-function
Open on GitHub

What vercel-edge-function does

The command inspects the project layout, including Vercel and Next.js configuration, API routes, middleware, package.json, TypeScript settings, and environment variables. It then drafts a TypeScript Edge Function with GET and POST handlers, request metadata, body validation, and JSON responses. A unit-test directory is part of the proposed layout.

The examples cover country-based content and JWT verification using jose, with a token read from a header or cookie. The authentication template falls back to a placeholder JWT_SECRET, so replace it with the project’s secret before relying on that check. Arguments let you request geolocation, authentication, data transformation, or proxy behavior.

What it helps with

  • scaffold a Next.js Edge Function
  • add JSON GET and POST handlers
  • serve content based on request country
  • check a JWT from a header or cookie

Example prompts for the agent

  • Create a user-profile Edge Function with authentication checks: /vercel-edge-function user-profile --auth
  • Generate regional-content using request geolocation: /vercel-edge-function regional-content --geo
  • Create data-proxy with data transformation: /vercel-edge-function data-proxy --transform --proxy

How to install vercel-edge-function

1claude-code-templates

npx claude-code-templates@latest --command nextjs-vercel/vercel-edge-function

2Manually: put the files in place

.claude/commands/vercel-edge-function.md

Call it with /vercel-edge-function. For all projects use ~/.claude/commands/

Questions about vercel-edge-function

How do I install vercel-edge-function in Claude Code?

Run npx claude-code-templates@latest --command nextjs-vercel/vercel-edge-function. The command file is placed at .claude/commands/vercel-edge-function.md.

What does the vercel-edge-function command do?

It inspects a Next.js project and drafts a TypeScript Vercel Edge Function. The template includes request handlers, with separate examples for geolocation and JWT authentication.

Which library does the authentication example use?

The JWT example uses jose and reads the secret from JWT_SECRET. It includes a fallback secret value that should be replaced with the project’s own secret.

Source text

vercel-edge-function.md · MIT
---
allowed-tools: Read, Write, Edit
argument-hint: "[function-name] [--auth] [--geo] [--transform] [--proxy]"
description: Generate optimized Vercel Edge Functions with geolocation, authentication, and data transformation
---

## 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.

© davila7 · MIT · davila7/claude-code-templates

Similar slash commands

All slash commands for Backend and APIs →

From the same set495

The whole set →

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor