Vercel Functions

サーバーレスVercelEdge RuntimeNext.jsフルスタックWeb開発

プラットフォーム

Vercel Functions

概要

Vercel Functionsは、Vercelが提供するサーバーレス関数実行環境として、フロントエンドアプリケーションとシームレスに統合し、API エンドポイント、ミドルウェア、バックエンド処理を簡単に構築できる革新的なプラットフォームです。Next.js、React、Vue.js、Svelte等のモダンWebフレームワークとの深い統合により、フルスタック開発の効率性を大幅に向上させています。Edge RuntimeとNode.js Runtimeの両方をサポートし、TypeScript、ゼロ設定、ストリーミング対応などの開発者体験を重視した機能により、2025年においてJAMstackアーキテクチャの中核的存在として注目を集めています。

詳細

Vercel Functions 2025年版は、モダンWeb開発のためのサーバーレスコンピューティングプラットフォームとして、特にNext.js、React アプリケーションでの利用が急拡大しています。Edge Runtimeによる全世界14ヶ所のエッジロケーションでの高速実行、TypeScriptネイティブサポート、ゼロ設定によるシームレスなデプロイメント、リアルタイムストリーミング機能など、フロントエンド開発者にとって理想的な環境を提供。Vercel AI SDKとの統合によりAIアプリケーション開発も強力にサポートし、Gitベースのワークフロー、プレビューデプロイメント、段階的ロールアウトなど、チーム開発を支援する豊富な機能を備えています。

主な特徴

  • Edge Runtime: 全世界のエッジでの高速実行
  • Node.js サポート: 従来のNode.jsランタイム完全対応
  • TypeScript ネイティブ: 型安全な開発環境
  • ゼロ設定: 設定不要のシームレスデプロイ
  • ストリーミング: リアルタイムデータ配信対応
  • Next.js 統合: フルスタック開発の最適化

メリット・デメリット

メリット

  • Next.jsエコシステムとの完璧な統合による開発効率の向上
  • Edge Runtimeによる全世界的な低遅延実行環境
  • TypeScriptネイティブサポートによる型安全な開発体験
  • Gitベースの自動デプロイとプレビュー機能
  • Vercel AI SDKとの統合によるAIアプリケーション開発支援
  • ゼロ設定でのシームレスなスケーリングと運用

デメリット

  • Vercelプラットフォームへの強い依存とベンダーロックイン
  • 他のクラウドプロバイダーと比較して限定的なインフラストラクチャー制御
  • 大量同時実行時のコスト予測が困難な課金体系
  • Edge Runtime環境での Node.js API制限
  • エンタープライズ機能や高度なカスタマイズ性の不足
  • 複雑なバックエンドロジックには機能制約がある

参考ページ

書き方の例

セットアップと関数作成

# Vercel CLIのインストール
npm install -g vercel

# プロジェクトの初期化とデプロイ
npx create-next-app@latest my-vercel-app
cd my-vercel-app
vercel

# ローカル開発サーバーの起動
vercel dev
// api/hello.js - 基本的なAPI関数
export default function handler(req, res) {
    console.log('Method:', req.method);
    console.log('Query:', req.query);
    console.log('Headers:', req.headers);
    
    // CORS対応
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
    
    if (req.method === 'OPTIONS') {
        res.status(200).end();
        return;
    }
    
    try {
        const response = {
            message: 'Hello from Vercel Functions!',
            timestamp: new Date().toISOString(),
            method: req.method,
            query: req.query,
            userAgent: req.headers['user-agent'] || 'Unknown'
        };
        
        res.status(200).json(response);
    } catch (error) {
        console.error('Function error:', error);
        res.status(500).json({ 
            error: 'Internal server error',
            message: error.message 
        });
    }
}
// api/users/[id].ts - TypeScript動的ルーティング
import { NextApiRequest, NextApiResponse } from 'next';

interface User {
    id: string;
    name: string;
    email: string;
    createdAt: string;
}

interface ApiResponse {
    success: boolean;
    data?: User;
    error?: string;
}

export default async function handler(
    req: NextApiRequest,
    res: NextApiResponse<ApiResponse>
) {
    const { id } = req.query;
    
    if (!id || typeof id !== 'string') {
        return res.status(400).json({
            success: false,
            error: 'Invalid user ID'
        });
    }
    
    try {
        switch (req.method) {
            case 'GET':
                const user = await getUserById(id);
                if (!user) {
                    return res.status(404).json({
                        success: false,
                        error: 'User not found'
                    });
                }
                return res.status(200).json({
                    success: true,
                    data: user
                });
                
            case 'PUT':
                const updatedUser = await updateUser(id, req.body);
                return res.status(200).json({
                    success: true,
                    data: updatedUser
                });
                
            case 'DELETE':
                await deleteUser(id);
                return res.status(204).end();
                
            default:
                res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
                return res.status(405).json({
                    success: false,
                    error: `Method ${req.method} not allowed`
                });
        }
    } catch (error) {
        console.error('API error:', error);
        return res.status(500).json({
            success: false,
            error: 'Internal server error'
        });
    }
}

async function getUserById(id: string): Promise<User | null> {
    // データベースからユーザー取得のシミュレーション
    return {
        id,
        name: `User ${id}`,
        email: `user${id}@example.com`,
        createdAt: new Date().toISOString()
    };
}

async function updateUser(id: string, data: Partial<User>): Promise<User> {
    // ユーザー更新のシミュレーション
    return {
        id,
        name: data.name || `User ${id}`,
        email: data.email || `user${id}@example.com`,
        createdAt: new Date().toISOString()
    };
}

async function deleteUser(id: string): Promise<void> {
    // ユーザー削除のシミュレーション
    console.log(`User ${id} deleted`);
}

HTTP APIとリクエスト処理

// api/posts/index.ts - RESTful API エンドポイント
import { NextApiRequest, NextApiResponse } from 'next';

interface Post {
    id: string;
    title: string;
    content: string;
    author: string;
    publishedAt: string;
    tags: string[];
}

interface CreatePostRequest {
    title: string;
    content: string;
    author: string;
    tags?: string[];
}

interface PostsApiResponse {
    success: boolean;
    data?: Post | Post[];
    error?: string;
    pagination?: {
        page: number;
        limit: number;
        total: number;
        totalPages: number;
    };
}

export default async function handler(
    req: NextApiRequest,
    res: NextApiResponse<PostsApiResponse>
) {
    try {
        switch (req.method) {
            case 'GET':
                return await handleGetPosts(req, res);
            case 'POST':
                return await handleCreatePost(req, res);
            default:
                res.setHeader('Allow', ['GET', 'POST']);
                return res.status(405).json({
                    success: false,
                    error: `Method ${req.method} not allowed`
                });
        }
    } catch (error) {
        console.error('Posts API error:', error);
        return res.status(500).json({
            success: false,
            error: 'Internal server error'
        });
    }
}

async function handleGetPosts(
    req: NextApiRequest,
    res: NextApiResponse<PostsApiResponse>
) {
    const page = parseInt(req.query.page as string) || 1;
    const limit = parseInt(req.query.limit as string) || 10;
    const search = req.query.search as string;
    const tag = req.query.tag as string;
    
    // クエリパラメータバリデーション
    if (page < 1 || limit < 1 || limit > 100) {
        return res.status(400).json({
            success: false,
            error: 'Invalid pagination parameters'
        });
    }
    
    try {
        const { posts, total } = await fetchPosts({
            page,
            limit,
            search,
            tag
        });
        
        const totalPages = Math.ceil(total / limit);
        
        return res.status(200).json({
            success: true,
            data: posts,
            pagination: {
                page,
                limit,
                total,
                totalPages
            }
        });
    } catch (error) {
        console.error('Fetch posts error:', error);
        return res.status(500).json({
            success: false,
            error: 'Failed to fetch posts'
        });
    }
}

async function handleCreatePost(
    req: NextApiRequest,
    res: NextApiResponse<PostsApiResponse>
) {
    const { title, content, author, tags = [] }: CreatePostRequest = req.body;
    
    // リクエストボディバリデーション
    if (!title || !content || !author) {
        return res.status(400).json({
            success: false,
            error: 'Title, content, and author are required'
        });
    }
    
    if (title.length > 200 || content.length > 10000) {
        return res.status(400).json({
            success: false,
            error: 'Title or content too long'
        });
    }
    
    try {
        const newPost = await createPost({
            title: title.trim(),
            content: content.trim(),
            author: author.trim(),
            tags: tags.map(tag => tag.trim()).filter(Boolean)
        });
        
        return res.status(201).json({
            success: true,
            data: newPost
        });
    } catch (error) {
        console.error('Create post error:', error);
        return res.status(500).json({
            success: false,
            error: 'Failed to create post'
        });
    }
}

async function fetchPosts({ page, limit, search, tag }: {
    page: number;
    limit: number;
    search?: string;
    tag?: string;
}): Promise<{ posts: Post[]; total: number }> {
    // データベースからの投稿取得をシミュレーション
    const mockPosts: Post[] = Array.from({ length: limit }, (_, i) => ({
        id: `post-${page}-${i + 1}`,
        title: `Sample Post ${page}-${i + 1}`,
        content: `This is the content of post ${page}-${i + 1}`,
        author: `Author ${i + 1}`,
        publishedAt: new Date().toISOString(),
        tags: ['technology', 'web-development']
    }));
    
    // 検索とタグフィルタリングをシミュレーション
    let filteredPosts = mockPosts;
    
    if (search) {
        filteredPosts = filteredPosts.filter(post => 
            post.title.toLowerCase().includes(search.toLowerCase()) ||
            post.content.toLowerCase().includes(search.toLowerCase())
        );
    }
    
    if (tag) {
        filteredPosts = filteredPosts.filter(post => 
            post.tags.includes(tag)
        );
    }
    
    return {
        posts: filteredPosts,
        total: 1000 // 総数をシミュレーション
    };
}

async function createPost(postData: CreatePostRequest): Promise<Post> {
    // 新規投稿作成をシミュレーション
    const newPost: Post = {
        id: `post-${Date.now()}`,
        title: postData.title,
        content: postData.content,
        author: postData.author,
        publishedAt: new Date().toISOString(),
        tags: postData.tags || []
    };
    
    // データベース保存のシミュレーション
    console.log('Creating post:', newPost);
    
    return newPost;
}

データベース統合とデータ処理

// api/database/users.ts - データベース統合例
import { NextApiRequest, NextApiResponse } from 'next';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

interface DatabaseResponse {
    success: boolean;
    data?: any;
    error?: string;
}

export default async function handler(
    req: NextApiRequest,
    res: NextApiResponse<DatabaseResponse>
) {
    try {
        switch (req.method) {
            case 'GET':
                return await handleGetUsers(req, res);
            case 'POST':
                return await handleCreateUser(req, res);
            default:
                res.setHeader('Allow', ['GET', 'POST']);
                return res.status(405).json({
                    success: false,
                    error: `Method ${req.method} not allowed`
                });
        }
    } catch (error) {
        console.error('Database API error:', error);
        return res.status(500).json({
            success: false,
            error: 'Database operation failed'
        });
    } finally {
        await prisma.$disconnect();
    }
}

async function handleGetUsers(
    req: NextApiRequest,
    res: NextApiResponse<DatabaseResponse>
) {
    const { page = '1', limit = '10', search = '' } = req.query;
    
    const pageNum = parseInt(page as string);
    const limitNum = parseInt(limit as string);
    const skip = (pageNum - 1) * limitNum;
    
    try {
        const whereClause = search ? {
            OR: [
                { name: { contains: search as string, mode: 'insensitive' } },
                { email: { contains: search as string, mode: 'insensitive' } }
            ]
        } : {};
        
        const [users, totalCount] = await Promise.all([
            prisma.user.findMany({
                where: whereClause,
                skip,
                take: limitNum,
                select: {
                    id: true,
                    name: true,
                    email: true,
                    createdAt: true,
                    profile: {
                        select: {
                            bio: true,
                            avatar: true
                        }
                    },
                    _count: {
                        select: {
                            posts: true,
                            comments: true
                        }
                    }
                }
            }),
            prisma.user.count({ where: whereClause })
        ]);
        
        return res.status(200).json({
            success: true,
            data: {
                users,
                pagination: {
                    page: pageNum,
                    limit: limitNum,
                    total: totalCount,
                    totalPages: Math.ceil(totalCount / limitNum)
                }
            }
        });
    } catch (error) {
        console.error('Get users error:', error);
        return res.status(500).json({
            success: false,
            error: 'Failed to fetch users'
        });
    }
}

async function handleCreateUser(
    req: NextApiRequest,
    res: NextApiResponse<DatabaseResponse>
) {
    const { name, email, bio, avatar } = req.body;
    
    // バリデーション
    if (!name || !email) {
        return res.status(400).json({
            success: false,
            error: 'Name and email are required'
        });
    }
    
    // メールアドレスの形式チェック
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(email)) {
        return res.status(400).json({
            success: false,
            error: 'Invalid email format'
        });
    }
    
    try {
        // トランザクションでユーザーとプロフィールを作成
        const newUser = await prisma.$transaction(async (tx) => {
            // メール重複チェック
            const existingUser = await tx.user.findUnique({
                where: { email }
            });
            
            if (existingUser) {
                throw new Error('Email already exists');
            }
            
            // ユーザー作成
            const user = await tx.user.create({
                data: {
                    name,
                    email,
                    profile: bio || avatar ? {
                        create: {
                            bio: bio || null,
                            avatar: avatar || null
                        }
                    } : undefined
                },
                include: {
                    profile: true
                }
            });
            
            return user;
        });
        
        return res.status(201).json({
            success: true,
            data: newUser
        });
    } catch (error) {
        console.error('Create user error:', error);
        
        if (error.message === 'Email already exists') {
            return res.status(409).json({
                success: false,
                error: 'Email already exists'
            });
        }
        
        return res.status(500).json({
            success: false,
            error: 'Failed to create user'
        });
    }
}

認証とセキュリティ

// api/auth/middleware.ts - 認証ミドルウェア
import { NextApiRequest, NextApiResponse } from 'next';
import jwt from 'jsonwebtoken';

interface AuthenticatedRequest extends NextApiRequest {
    user?: {
        id: string;
        email: string;
        role: string;
    };
}

export interface AuthMiddlewareOptions {
    requiredRole?: string;
    requireAuth?: boolean;
}

export function withAuth(
    handler: (req: AuthenticatedRequest, res: NextApiResponse) => Promise<void>,
    options: AuthMiddlewareOptions = {}
) {
    return async (req: AuthenticatedRequest, res: NextApiResponse) => {
        try {
            // CORS ヘッダーの設定
            res.setHeader('Access-Control-Allow-Origin', process.env.ALLOWED_ORIGINS || '*');
            res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
            res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
            
            if (req.method === 'OPTIONS') {
                return res.status(200).end();
            }
            
            // 認証が必要でない場合はそのまま実行
            if (!options.requireAuth) {
                return await handler(req, res);
            }
            
            // トークンの取得
            const token = extractToken(req);
            if (!token) {
                return res.status(401).json({
                    success: false,
                    error: 'Authorization token required'
                });
            }
            
            // トークンの検証
            const decoded = await verifyToken(token);
            req.user = decoded;
            
            // ロールベースの認可
            if (options.requiredRole && decoded.role !== options.requiredRole) {
                return res.status(403).json({
                    success: false,
                    error: 'Insufficient permissions'
                });
            }
            
            // レート制限の実装
            const rateLimitResult = await checkRateLimit(req);
            if (!rateLimitResult.allowed) {
                return res.status(429).json({
                    success: false,
                    error: 'Rate limit exceeded',
                    retryAfter: rateLimitResult.retryAfter
                });
            }
            
            return await handler(req, res);
        } catch (error) {
            console.error('Auth middleware error:', error);
            
            if (error.name === 'JsonWebTokenError') {
                return res.status(401).json({
                    success: false,
                    error: 'Invalid token'
                });
            }
            
            if (error.name === 'TokenExpiredError') {
                return res.status(401).json({
                    success: false,
                    error: 'Token expired'
                });
            }
            
            return res.status(500).json({
                success: false,
                error: 'Authentication failed'
            });
        }
    };
}

function extractToken(req: NextApiRequest): string | null {
    const authHeader = req.headers.authorization;
    if (!authHeader) return null;
    
    const parts = authHeader.split(' ');
    if (parts.length !== 2 || parts[0] !== 'Bearer') return null;
    
    return parts[1];
}

async function verifyToken(token: string): Promise<{
    id: string;
    email: string;
    role: string;
}> {
    const secret = process.env.JWT_SECRET;
    if (!secret) {
        throw new Error('JWT_SECRET not configured');
    }
    
    const decoded = jwt.verify(token, secret) as any;
    
    return {
        id: decoded.sub,
        email: decoded.email,
        role: decoded.role || 'user'
    };
}

interface RateLimitResult {
    allowed: boolean;
    retryAfter?: number;
}

async function checkRateLimit(req: NextApiRequest): Promise<RateLimitResult> {
    // IPアドレスベースのレート制限(簡易実装)
    const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
    const key = `rate_limit:${ip}`;
    
    // Redis或いはメモリキャッシュでの実装をシミュレート
    // 実際の実装では、Vercel KVやUpstash Redisを使用
    
    return { allowed: true }; // 簡易実装のため常に許可
}
// api/auth/login.ts - ログイン認証
import { NextApiRequest, NextApiResponse } from 'next';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

interface LoginRequest {
    email: string;
    password: string;
}

interface LoginResponse {
    success: boolean;
    data?: {
        token: string;
        user: {
            id: string;
            name: string;
            email: string;
            role: string;
        };
    };
    error?: string;
}

export default async function handler(
    req: NextApiRequest,
    res: NextApiResponse<LoginResponse>
) {
    if (req.method !== 'POST') {
        res.setHeader('Allow', ['POST']);
        return res.status(405).json({
            success: false,
            error: 'Method not allowed'
        });
    }
    
    try {
        const { email, password }: LoginRequest = req.body;
        
        // バリデーション
        if (!email || !password) {
            return res.status(400).json({
                success: false,
                error: 'Email and password are required'
            });
        }
        
        // ユーザー検索
        const user = await prisma.user.findUnique({
            where: { email },
            include: {
                profile: true
            }
        });
        
        if (!user) {
            return res.status(401).json({
                success: false,
                error: 'Invalid credentials'
            });
        }
        
        // パスワード検証
        const isValidPassword = await bcrypt.compare(password, user.passwordHash);
        if (!isValidPassword) {
            return res.status(401).json({
                success: false,
                error: 'Invalid credentials'
            });
        }
        
        // JWTトークン生成
        const token = jwt.sign(
            {
                sub: user.id,
                email: user.email,
                role: user.role,
                iat: Math.floor(Date.now() / 1000)
            },
            process.env.JWT_SECRET!,
            { expiresIn: '24h' }
        );
        
        // ログイン記録
        await prisma.loginLog.create({
            data: {
                userId: user.id,
                ipAddress: req.headers['x-forwarded-for'] as string || 'unknown',
                userAgent: req.headers['user-agent'] || 'unknown',
                loginAt: new Date()
            }
        });
        
        return res.status(200).json({
            success: true,
            data: {
                token,
                user: {
                    id: user.id,
                    name: user.name,
                    email: user.email,
                    role: user.role
                }
            }
        });
        
    } catch (error) {
        console.error('Login error:', error);
        return res.status(500).json({
            success: false,
            error: 'Login failed'
        });
    } finally {
        await prisma.$disconnect();
    }
}

イベント駆動アーキテクチャ

// api/webhook/stripe.ts - Stripeウェブフック処理
import { NextApiRequest, NextApiResponse } from 'next';
import Stripe from 'stripe';
import { buffer } from 'micro';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
    apiVersion: '2023-10-16',
});

// Next.jsのbody parserを無効化
export const config = {
    api: {
        bodyParser: false,
    },
};

interface WebhookResponse {
    success: boolean;
    message?: string;
    error?: string;
}

export default async function handler(
    req: NextApiRequest,
    res: NextApiResponse<WebhookResponse>
) {
    if (req.method !== 'POST') {
        res.setHeader('Allow', ['POST']);
        return res.status(405).json({
            success: false,
            error: 'Method not allowed'
        });
    }
    
    try {
        // リクエストボディを取得
        const buf = await buffer(req);
        const signature = req.headers['stripe-signature'] as string;
        
        if (!signature) {
            return res.status(400).json({
                success: false,
                error: 'Missing Stripe signature'
            });
        }
        
        // Stripeイベントの検証
        const event = stripe.webhooks.constructEvent(
            buf,
            signature,
            process.env.STRIPE_WEBHOOK_SECRET!
        );
        
        console.log('Stripe event received:', event.type);
        
        // イベントタイプに応じた処理
        switch (event.type) {
            case 'payment_intent.succeeded':
                await handlePaymentSucceeded(event.data.object);
                break;
                
            case 'payment_intent.payment_failed':
                await handlePaymentFailed(event.data.object);
                break;
                
            case 'customer.subscription.created':
                await handleSubscriptionCreated(event.data.object);
                break;
                
            case 'customer.subscription.deleted':
                await handleSubscriptionDeleted(event.data.object);
                break;
                
            case 'invoice.payment_succeeded':
                await handleInvoicePaymentSucceeded(event.data.object);
                break;
                
            default:
                console.log(`Unhandled event type: ${event.type}`);
        }
        
        return res.status(200).json({
            success: true,
            message: 'Webhook processed successfully'
        });
        
    } catch (error) {
        console.error('Stripe webhook error:', error);
        
        if (error instanceof Error && error.message.includes('signature')) {
            return res.status(400).json({
                success: false,
                error: 'Invalid signature'
            });
        }
        
        return res.status(500).json({
            success: false,
            error: 'Webhook processing failed'
        });
    }
}

async function handlePaymentSucceeded(paymentIntent: any) {
    console.log('Payment succeeded:', paymentIntent.id);
    
    try {
        // 注文状態の更新
        await updateOrderStatus(paymentIntent.metadata.orderId, 'paid');
        
        // 顧客への確認メール送信
        await sendPaymentConfirmationEmail(
            paymentIntent.metadata.customerEmail,
            paymentIntent.amount,
            paymentIntent.currency
        );
        
        // 在庫の更新
        await updateInventory(paymentIntent.metadata.orderId);
        
        // 売上分析データの記録
        await recordSalesAnalytics(paymentIntent);
        
    } catch (error) {
        console.error('Payment succeeded handler error:', error);
    }
}

async function handlePaymentFailed(paymentIntent: any) {
    console.log('Payment failed:', paymentIntent.id);
    
    try {
        // 注文状態の更新
        await updateOrderStatus(paymentIntent.metadata.orderId, 'payment_failed');
        
        // 顧客への失敗通知
        await sendPaymentFailedEmail(
            paymentIntent.metadata.customerEmail,
            paymentIntent.last_payment_error?.message || 'Payment failed'
        );
        
        // 再試行のスケジューリング
        await schedulePaymentRetry(paymentIntent.metadata.orderId);
        
    } catch (error) {
        console.error('Payment failed handler error:', error);
    }
}

async function handleSubscriptionCreated(subscription: any) {
    console.log('Subscription created:', subscription.id);
    
    try {
        // ユーザーの権限アップグレード
        await upgradeUserPermissions(subscription.customer, subscription.items.data[0].price.id);
        
        // ウェルカムメールの送信
        await sendWelcomeEmail(subscription.customer);
        
        // 課金分析データの記録
        await recordSubscriptionAnalytics(subscription);
        
    } catch (error) {
        console.error('Subscription created handler error:', error);
    }
}

async function updateOrderStatus(orderId: string, status: string) {
    // データベースの注文状態更新をシミュレート
    console.log(`Updating order ${orderId} status to ${status}`);
}

async function sendPaymentConfirmationEmail(email: string, amount: number, currency: string) {
    // メール送信をシミュレート
    console.log(`Sending confirmation email to ${email} for ${amount} ${currency}`);
}

async function updateInventory(orderId: string) {
    // 在庫更新をシミュレート
    console.log(`Updating inventory for order ${orderId}`);
}

async function recordSalesAnalytics(paymentIntent: any) {
    // 売上分析データ記録をシミュレート
    console.log('Recording sales analytics:', paymentIntent.id);
}

監視とパフォーマンス最適化

// api/monitoring/health.ts - ヘルスチェックとモニタリング
import { NextApiRequest, NextApiResponse } from 'next';

interface HealthCheckResponse {
    status: 'healthy' | 'unhealthy';
    timestamp: string;
    uptime: number;
    version: string;
    checks: {
        database: { status: 'up' | 'down'; responseTime?: number };
        redis: { status: 'up' | 'down'; responseTime?: number };
        external_api: { status: 'up' | 'down'; responseTime?: number };
    };
    memory: {
        used: number;
        total: number;
        percentage: number;
    };
    performance: {
        averageResponseTime: number;
        requestCount: number;
        errorRate: number;
    };
}

export default async function handler(
    req: NextApiRequest,
    res: NextApiResponse<HealthCheckResponse>
) {
    const startTime = Date.now();
    
    try {
        // 各種システムの健全性チェック
        const [databaseHealth, redisHealth, externalApiHealth] = await Promise.allSettled([
            checkDatabaseHealth(),
            checkRedisHealth(),
            checkExternalApiHealth()
        ]);
        
        // メモリ使用量の取得
        const memoryUsage = process.memoryUsage();
        const memoryTotal = memoryUsage.heapTotal;
        const memoryUsed = memoryUsage.heapUsed;
        const memoryPercentage = (memoryUsed / memoryTotal) * 100;
        
        // パフォーマンス情報の取得
        const performanceData = await getPerformanceMetrics();
        
        // 全体的な健全性の判定
        const allHealthy = [databaseHealth, redisHealth, externalApiHealth]
            .every(result => result.status === 'fulfilled' && result.value.status === 'up');
        
        const healthResponse: HealthCheckResponse = {
            status: allHealthy ? 'healthy' : 'unhealthy',
            timestamp: new Date().toISOString(),
            uptime: process.uptime(),
            version: process.env.npm_package_version || '1.0.0',
            checks: {
                database: databaseHealth.status === 'fulfilled' ? databaseHealth.value : { status: 'down' },
                redis: redisHealth.status === 'fulfilled' ? redisHealth.value : { status: 'down' },
                external_api: externalApiHealth.status === 'fulfilled' ? externalApiHealth.value : { status: 'down' }
            },
            memory: {
                used: Math.round(memoryUsed / 1024 / 1024), // MB
                total: Math.round(memoryTotal / 1024 / 1024), // MB
                percentage: Math.round(memoryPercentage * 100) / 100
            },
            performance: performanceData
        };
        
        // 応答時間の記録
        const responseTime = Date.now() - startTime;
        await recordMetric('health_check_response_time', responseTime);
        
        const statusCode = allHealthy ? 200 : 503;
        return res.status(statusCode).json(healthResponse);
        
    } catch (error) {
        console.error('Health check error:', error);
        
        return res.status(503).json({
            status: 'unhealthy',
            timestamp: new Date().toISOString(),
            uptime: process.uptime(),
            version: process.env.npm_package_version || '1.0.0',
            checks: {
                database: { status: 'down' },
                redis: { status: 'down' },
                external_api: { status: 'down' }
            },
            memory: {
                used: 0,
                total: 0,
                percentage: 0
            },
            performance: {
                averageResponseTime: 0,
                requestCount: 0,
                errorRate: 100
            }
        });
    }
}

async function checkDatabaseHealth(): Promise<{ status: 'up' | 'down'; responseTime?: number }> {
    const startTime = Date.now();
    
    try {
        // データベース接続テスト(例:簡単なクエリ実行)
        // await prisma.$queryRaw`SELECT 1`;
        
        const responseTime = Date.now() - startTime;
        return { status: 'up', responseTime };
    } catch (error) {
        console.error('Database health check failed:', error);
        return { status: 'down' };
    }
}

async function checkRedisHealth(): Promise<{ status: 'up' | 'down'; responseTime?: number }> {
    const startTime = Date.now();
    
    try {
        // Redis接続テスト
        // await redis.ping();
        
        const responseTime = Date.now() - startTime;
        return { status: 'up', responseTime };
    } catch (error) {
        console.error('Redis health check failed:', error);
        return { status: 'down' };
    }
}

async function checkExternalApiHealth(): Promise<{ status: 'up' | 'down'; responseTime?: number }> {
    const startTime = Date.now();
    
    try {
        // 外部API接続テスト
        const response = await fetch('https://api.example.com/health', {
            method: 'GET',
            timeout: 5000
        });
        
        const responseTime = Date.now() - startTime;
        
        if (response.ok) {
            return { status: 'up', responseTime };
        } else {
            return { status: 'down' };
        }
    } catch (error) {
        console.error('External API health check failed:', error);
        return { status: 'down' };
    }
}

async function getPerformanceMetrics(): Promise<{
    averageResponseTime: number;
    requestCount: number;
    errorRate: number;
}> {
    // パフォーマンスメトリクスの取得をシミュレート
    // 実際の実装では、メトリクス収集システムから取得
    return {
        averageResponseTime: 150, // ms
        requestCount: 1250,
        errorRate: 2.5 // %
    };
}

async function recordMetric(metricName: string, value: number): Promise<void> {
    // メトリクス記録をシミュレート
    // 実際の実装では、CloudWatch、DataDog、または Vercel Analytics に送信
    console.log(`Metric recorded: ${metricName} = ${value}`);
}

デプロイメントと本番運用

// vercel.json - Vercel設定ファイル
{
    "functions": {
        "api/**/*.ts": {
            "runtime": "@vercel/[email protected]"
        },
        "api/edge/**/*.ts": {
            "runtime": "edge"
        }
    },
    "regions": ["iad1", "hnd1", "lhr1"],
    "env": {
        "DATABASE_URL": "@database-url",
        "JWT_SECRET": "@jwt-secret",
        "STRIPE_SECRET_KEY": "@stripe-secret",
        "REDIS_URL": "@redis-url"
    },
    "build": {
        "env": {
            "NODE_ENV": "production"
        }
    },
    "headers": [
        {
            "source": "/api/(.*)",
            "headers": [
                {
                    "key": "Access-Control-Allow-Origin",
                    "value": "*"
                },
                {
                    "key": "Access-Control-Allow-Methods",
                    "value": "GET, POST, PUT, DELETE, OPTIONS"
                },
                {
                    "key": "Access-Control-Allow-Headers",
                    "value": "Content-Type, Authorization"
                }
            ]
        }
    ],
    "rewrites": [
        {
            "source": "/api/v1/(.*)",
            "destination": "/api/$1"
        }
    ]
}
#!/bin/bash
# deploy.sh - デプロイメントスクリプト

set -e

# 環境変数の設定
ENVIRONMENT=${1:-production}
PROJECT_NAME="my-vercel-app"

echo "Deploying to environment: ${ENVIRONMENT}"

# プロジェクトのビルドテスト
echo "Building project..."
npm run build

# 本番環境へのデプロイ
if [ "$ENVIRONMENT" == "production" ]; then
    echo "Deploying to production..."
    vercel --prod
elif [ "$ENVIRONMENT" == "preview" ]; then
    echo "Deploying to preview..."
    vercel
else
    echo "Invalid environment: ${ENVIRONMENT}"
    exit 1
fi

# デプロイ後のヘルスチェック
echo "Performing health check..."
if [ "$ENVIRONMENT" == "production" ]; then
    HEALTH_URL="https://${PROJECT_NAME}.vercel.app/api/monitoring/health"
else
    # プレビューURLを取得(簡略化)
    HEALTH_URL="https://preview-url/api/monitoring/health"
fi

# ヘルスチェック実行
curl -f ${HEALTH_URL} || {
    echo "Health check failed!"
    exit 1
}

echo "Deployment completed successfully!"
#!/bin/bash
# monitor.sh - 監視スクリプト

FUNCTION_URL="https://my-vercel-app.vercel.app"

echo "Monitoring Vercel Functions..."

# APIエンドポイントの監視
check_endpoint() {
    local endpoint=$1
    local expected_status=$2
    
    echo "Checking ${endpoint}..."
    
    response=$(curl -s -w "HTTPSTATUS:%{http_code};TIME:%{time_total}" ${FUNCTION_URL}${endpoint})
    
    http_status=$(echo $response | grep -o "HTTPSTATUS:[0-9]*" | cut -d: -f2)
    response_time=$(echo $response | grep -o "TIME:[0-9.]*" | cut -d: -f2)
    
    if [ "$http_status" == "$expected_status" ]; then
        echo "✓ ${endpoint} - Status: ${http_status}, Time: ${response_time}s"
    else
        echo "✗ ${endpoint} - Expected: ${expected_status}, Got: ${http_status}"
    fi
}

# 各エンドポイントの確認
check_endpoint "/api/monitoring/health" "200"
check_endpoint "/api/hello" "200"
check_endpoint "/api/users" "200"

# ログの確認(Vercel CLI使用)
echo "Fetching recent logs..."
vercel logs --output raw | tail -20

echo "Monitoring complete!"