10 KiB
10 KiB
🖼️ Интеграция @vercel/og с Quoter Proxy
📋 Обзор
@vercel/og - это мощная библиотека для генерации динамических OpenGraph изображений на Edge Runtime. Quoter теперь поддерживает интеграцию с @vercel/og для создания красивых социальных превью.
🔄 Изменения в архитектуре
Удалённая функциональность (Legacy)
- ❌
src/overlay.rs- удалена встроенная логика наложения текста - ❌
src/Muller-Regular.woff2- удалён встроенный шрифт - ❌
imageproc,ab_glyphdependencies - удалены из Cargo.toml - ❌ Параметр
s=<shout_id>в API - больше не поддерживается
Новая архитектура
- ✅ @vercel/og обрабатывает генерацию OpenGraph изображений
- ✅ Quoter выступает как proxy для статических файлов
- ✅ Improved caching и performance optimization
🚀 Настройка @vercel/og
1. Установка пакета
npm install @vercel/og
# или
yarn add @vercel/og
2. Базовый пример использования
import { ImageResponse } from '@vercel/og'
export default function handler(req: Request) {
return new ImageResponse(
(
<div
style={{
fontSize: 128,
background: 'white',
width: '100%',
height: '100%',
display: 'flex',
textAlign: 'center',
alignItems: 'center',
justifyContent: 'center',
}}
>
Hello world!
</div>
),
{
width: 1200,
height: 600,
},
)
}
🔗 Интеграция с Quoter Proxy
Сценарий использования
- @vercel/og генерирует динамические OpenGraph изображения
- Результат сохраняется через Quoter API
- Quoter обслуживает изображения с кэшированием и оптимизацией
Настройка Endpoint для @vercel/og
// pages/api/og/[...slug].ts
import { ImageResponse } from '@vercel/og'
import { NextRequest } from 'next/server'
export const config = {
runtime: 'edge',
}
export default async function handler(req: NextRequest) {
try {
const { searchParams } = new URL(req.url)
// Получение параметров
const title = searchParams.get('title') ?? 'Default Title'
const description = searchParams.get('description') ?? 'Default Description'
const imageUrl = searchParams.get('image') // URL изображения из Quoter
// Загрузка изображения через Quoter proxy
let backgroundImage = null
if (imageUrl) {
try {
const imageResponse = await fetch(imageUrl)
const imageBuffer = await imageResponse.arrayBuffer()
backgroundImage = `data:image/jpeg;base64,${Buffer.from(imageBuffer).toString('base64')}`
} catch (error) {
console.error('Failed to load image from Quoter:', error)
}
}
return new ImageResponse(
(
<div
style={{
background: backgroundImage
? `url(${backgroundImage})`
: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
backgroundSize: 'cover',
backgroundPosition: 'center',
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
fontFamily: 'system-ui',
}}
>
{/* Overlay для читаемости */}
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.4)',
}}
/>
{/* Контент */}
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
zIndex: 1,
padding: '40px',
textAlign: 'center',
}}
>
<h1
style={{
fontSize: '72px',
fontWeight: 'bold',
color: 'white',
margin: 0,
marginBottom: '20px',
textShadow: '2px 2px 4px rgba(0, 0, 0, 0.8)',
}}
>
{title}
</h1>
<p
style={{
fontSize: '36px',
color: '#f1f5f9',
margin: 0,
textShadow: '1px 1px 2px rgba(0, 0, 0, 0.8)',
}}
>
{description}
</p>
</div>
</div>
),
{
width: 1200,
height: 630,
}
)
} catch (e: any) {
console.log(`${e.message}`)
return new Response(`Failed to generate the image`, {
status: 500,
})
}
}
📤 Сохранение сгенерированных изображений в Quoter
Пример интеграции
// utils/saveToQuoter.ts
export async function saveOgImageToQuoter(
imageBuffer: Buffer,
filename: string,
token: string
): Promise<string> {
const formData = new FormData()
const blob = new Blob([imageBuffer], { type: 'image/png' })
formData.append('file', blob, filename)
const response = await fetch('https://quoter.staging.discours.io/', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
},
body: formData,
})
if (!response.ok) {
throw new Error(`Failed to upload to Quoter: ${response.statusText}`)
}
const result = await response.text()
return result // URL загруженного файла
}
// Использование
async function generateAndSaveOgImage(title: string, description: string, token: string) {
// Генерация изображения через @vercel/og
const ogResponse = await fetch(`/api/og?title=${encodeURIComponent(title)}&description=${encodeURIComponent(description)}`)
const imageBuffer = Buffer.from(await ogResponse.arrayBuffer())
// Сохранение в Quoter
const filename = `og-${Date.now()}.png`
const quoterUrl = await saveOgImageToQuoter(imageBuffer, filename, token)
return quoterUrl
}
🎨 Расширенные возможности
Кастомные шрифты
// Загрузка шрифтов
const font = fetch(
new URL('./assets/Muller-Regular.woff', import.meta.url)
).then((res) => res.arrayBuffer())
// Использование в ImageResponse
return new ImageResponse(
<div style={{ fontFamily: 'Muller' }}>
Custom font text
</div>,
{
width: 1200,
height: 630,
fonts: [
{
name: 'Muller',
data: await font,
style: 'normal',
},
],
}
)
Динамическая загрузка изображений из Quoter
async function loadQuoterImage(imageId: string): Promise<string> {
const quoterUrl = `https://quoter.staging.discours.io/${imageId}`
try {
const response = await fetch(quoterUrl)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
const buffer = await response.arrayBuffer()
return `data:image/jpeg;base64,${Buffer.from(buffer).toString('base64')}`
} catch (error) {
console.error('Failed to load image from Quoter:', error)
return '' // fallback
}
}
🔧 Конфигурация Quoter для @vercel/og
Environment Variables
# .env.local
QUOTER_API_URL=https://quoter.staging.discours.io
QUOTER_AUTH_TOKEN=your_jwt_token_here
Типы для TypeScript
// types/quoter.ts
export interface QuoterUploadResponse {
url: string
filename: string
size: number
contentType: string
}
export interface OgImageParams {
title: string
description?: string
backgroundImage?: string
template?: 'default' | 'article' | 'profile'
}
📊 Performance & Caching
Оптимизация производительности
- ✅ @vercel/og работает на Edge Runtime
- ✅ Quoter обеспечивает кэширование с ETag
- ✅ Автоматическое сжатие изображений
- ✅ CDN-дружественные HTTP заголовки
Рекомендации по кэшированию
// Добавление cache headers для OG изображений
export default function handler(req: NextRequest) {
const imageResponse = new ImageResponse(/* ... */)
// Кэширование на 1 день
imageResponse.headers.set('Cache-Control', 'public, max-age=86400, s-maxage=86400')
imageResponse.headers.set('CDN-Cache-Control', 'public, max-age=86400')
return imageResponse
}
🚀 Deployment
Vercel
- Deploy @vercel/og endpoints на Vercel
- Configure Quoter URL в environment variables
- Set up proper CORS если нужно
Standalone
- Use Next.js standalone mode
- Configure Quoter integration
- Deploy где угодно с Node.js support
🔍 Troubleshooting
Распространённые проблемы
-
"Failed to load image from Quoter"
- Проверьте доступность Quoter API
- Убедитесь в правильности токена авторизации
-
"Font loading failed"
- Убедитесь, что шрифты доступны в build time
- Используйте правильные MIME types
-
"Image generation timeout"
- Оптимизируйте сложность layout
- Уменьшите размер внешних ресурсов
📚 Дополнительные ресурсы
💋 Упрощение через разделение ответственности: @vercel/og занимается генерацией, Quoter - хранением и доставкой.