使用 Next.js 和 Sharp 构建 OpenGraph 图像 API
在今天的教程中,我将指导您使用 Next.js 和 Sharp 创建可用于生产的 OpenGraph 图像 API。此实现借鉴了我构建 Gleam.so 图像生成系统的经验,该系统现在每天处理数千个请求。
建立 API 基础
让我们从 Next.js 14 中的基本 API 路由结构开始:
// app/api/og/route.ts
import { NextResponse } from 'next/server';
import sharp from 'sharp';
import { ImageProcessor } from '../../../lib/image-processor';
export const runtime = 'edge';
export async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title');
const theme = searchParams.get('theme') || 'light';
if (!title) {
return new NextResponse('Missing title parameter', {
status: 400
});
}
const image = await generateOGImage({
title,
theme
});
return new NextResponse(image, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=31536000, immutable'
}
});
} catch (error) {
console.error('OG Generation failed:', error);
return new NextResponse('Image generation failed', {
status: 500
});
}
}实现图像处理
图片处理逻辑需要仔细考虑性能和质量。我们可以这样实现它:
// lib/image-processor.ts
import sharp from 'sharp';
export class ImageProcessor {
private readonly width = 1200;
private readonly height = 630;
private readonly padding = 60;
async generateOGImage(options: GenerationOptions): Promise {
const {
title,
theme = 'light',
template = 'default'
} = options;
// Create base image
const svg = await this.generateSVG({
title,
theme,
template
});
// Convert SVG to PNG with optimizations
return sharp(Buffer.from(svg))
.resize(this.width, this.height)
.png({
compressionLevel: 9,
quality: 80
})
.toBuffer();
}
private async generateSVG(options: SVGOptions): Promise {
const { title, theme } = options;
const backgroundColor = theme === 'light' ? '#ffffff' : '#1a1a1a';
const textColor = theme === 'light' ? '#000000' : '#ffffff';
return `
`;
}
private escapeHTML(text: string): string {
return text
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
} 实现错误处理
对于生产 API 来说,强大的错误处理至关重要。以下是一种全面的方法:
// lib/error-handler.ts
export class ErrorHandler {
async handleError(error: Error, context: ErrorContext): Promise {
// Log error with context
console.error('Image generation failed:', {
error,
context,
timestamp: new Date().toISOString()
});
// Generate fallback image
return this.generateFallbackImage(context);
}
private async generateFallbackImage(context: ErrorContext): Promise {
// Create a simple fallback image
const fallbackSVG = `
`;
return sharp(Buffer.from(fallbackSVG))
.png()
.toBuffer();
}
} 性能优化
为了确保最佳性能,让我们实现缓存和优化策略:
// lib/cache-manager.ts
import { Redis } from 'ioredis';
export class CacheManager {
private redis: Redis;
private readonly TTL = 7 * 24 * 60 * 60; // 1 week
constructor() {
this.redis = new Redis(process.env.REDIS_URL);
}
async getFromCache(key: string): Promise {
try {
const cached = await this.redis.get(key);
return cached ? Buffer.from(cached, 'base64') : null;
} catch (error) {
console.error('Cache fetch failed:', error);
return null;
}
}
async setInCache(key: string, image: Buffer): Promise {
try {
await this.redis.set(
key,
image.toString('base64'),
'EX',
this.TTL
);
} catch (error) {
console.error('Cache set failed:', error);
}
}
}
// Implementation in API route
import { CacheManager } from '../lib/cache-manager';
const cacheManager = new CacheManager();
export async function GET(request: Request) {
const cacheKey = generateCacheKey(request.url);
// Try cache first
const cached = await cacheManager.getFromCache(cacheKey);
if (cached) {
return new NextResponse(cached, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=31536000, immutable'
}
});
}
// Generate and cache if not found
const image = await generateOGImage(/* params */);
await cacheManager.setInCache(cacheKey, image);
return new NextResponse(image, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=31536000, immutable'
}
});
} 监控和分析
为了维护和改进系统,实施监控:
// lib/analytics.ts
export class Analytics {
async trackGeneration(params: {
duration: number;
success: boolean;
cached: boolean;
error?: Error;
}) {
await fetch(process.env.ANALYTICS_ENDPOINT, {
method: 'POST',
body: JSON.stringify({
event: 'og_generation',
...params,
timestamp: new Date().toISOString()
})
});
}
}使用 API
以下是在 Next.js 应用程序中使用 API 的方法:
// Example usage in your pages
const OGImage = ({ title }: { title: string }) => {
const ogImageUrl = `${process.env.NEXT_PUBLIC_API_URL}/api/og?title=${
encodeURIComponent(title)
}&theme=light`;
return (
);
};性能注意事项
确保最佳性能的几个关键点:
正在寻找现成的解决方案?
虽然构建自己的 API 很有教育意义,但您可能希望考虑使用 Gleam.so 来获得可用于生产的解决方案。它实现了所有这些最佳实践以及更多,从而节省了您的开发和维护时间。
问题?
在评论中提出你的问题!我在这里帮助你在自己的项目中实现这一点。