234 lines
6.1 KiB
Markdown
234 lines
6.1 KiB
Markdown
|
|
# 🚀 Quick Start: @vercel/og + Quoter
|
|||
|
|
|
|||
|
|
## ⚡ 5-минутная настройка
|
|||
|
|
|
|||
|
|
### 1. Установка зависимостей
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
npm install @vercel/og
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. Создание API endpoint
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// pages/api/og.tsx (Next.js)
|
|||
|
|
import { ImageResponse } from '@vercel/og'
|
|||
|
|
|
|||
|
|
export const config = { runtime: 'edge' }
|
|||
|
|
|
|||
|
|
export default function handler(req) {
|
|||
|
|
const { searchParams } = new URL(req.url)
|
|||
|
|
const title = searchParams.get('title') ?? 'Hello World'
|
|||
|
|
|
|||
|
|
return new ImageResponse(
|
|||
|
|
(
|
|||
|
|
<div style={{
|
|||
|
|
background: 'white',
|
|||
|
|
width: '100%',
|
|||
|
|
height: '100%',
|
|||
|
|
display: 'flex',
|
|||
|
|
alignItems: 'center',
|
|||
|
|
justifyContent: 'center',
|
|||
|
|
fontSize: 60,
|
|||
|
|
fontWeight: 700,
|
|||
|
|
}}>
|
|||
|
|
{title}
|
|||
|
|
</div>
|
|||
|
|
),
|
|||
|
|
{ width: 1200, height: 630 }
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. Интеграция с Quoter
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// utils/quoter.ts
|
|||
|
|
export async function uploadToQuoter(imageBuffer: Buffer, filename: string, token: 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,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return response.text() // URL файла
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. Использование
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// Генерация OG изображения
|
|||
|
|
const ogResponse = await fetch('/api/og?title=My%20Amazing%20Post')
|
|||
|
|
const imageBuffer = Buffer.from(await ogResponse.arrayBuffer())
|
|||
|
|
|
|||
|
|
// Загрузка в Quoter
|
|||
|
|
const quoterUrl = await uploadToQuoter(imageBuffer, 'og-image.png', userToken)
|
|||
|
|
|
|||
|
|
// Использование в meta tags
|
|||
|
|
<meta property="og:image" content={quoterUrl} />
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🎨 Расширенный пример с изображением фона
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// pages/api/og-advanced.tsx
|
|||
|
|
import { ImageResponse } from '@vercel/og'
|
|||
|
|
|
|||
|
|
export default async function handler(req) {
|
|||
|
|
const { searchParams } = new URL(req.url)
|
|||
|
|
const title = searchParams.get('title')
|
|||
|
|
const imageUrl = searchParams.get('image') // Quoter URL
|
|||
|
|
|
|||
|
|
// Загружаем изображение из Quoter
|
|||
|
|
let backgroundImage = null
|
|||
|
|
if (imageUrl) {
|
|||
|
|
const imageResponse = await fetch(imageUrl)
|
|||
|
|
const buffer = await imageResponse.arrayBuffer()
|
|||
|
|
backgroundImage = `data:image/jpeg;base64,${Buffer.from(buffer).toString('base64')}`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return new ImageResponse(
|
|||
|
|
(
|
|||
|
|
<div style={{
|
|||
|
|
background: backgroundImage ? `url(${backgroundImage})` : '#667eea',
|
|||
|
|
backgroundSize: 'cover',
|
|||
|
|
width: '100%',
|
|||
|
|
height: '100%',
|
|||
|
|
display: 'flex',
|
|||
|
|
flexDirection: 'column',
|
|||
|
|
alignItems: 'center',
|
|||
|
|
justifyContent: 'center',
|
|||
|
|
}}>
|
|||
|
|
{/* Затемнение для читаемости */}
|
|||
|
|
<div style={{
|
|||
|
|
position: 'absolute',
|
|||
|
|
inset: 0,
|
|||
|
|
background: 'rgba(0,0,0,0.4)',
|
|||
|
|
}} />
|
|||
|
|
|
|||
|
|
{/* Заголовок */}
|
|||
|
|
<h1 style={{
|
|||
|
|
fontSize: 72,
|
|||
|
|
fontWeight: 'bold',
|
|||
|
|
color: 'white',
|
|||
|
|
textAlign: 'center',
|
|||
|
|
textShadow: '2px 2px 4px rgba(0,0,0,0.8)',
|
|||
|
|
zIndex: 1,
|
|||
|
|
}}>
|
|||
|
|
{title}
|
|||
|
|
</h1>
|
|||
|
|
</div>
|
|||
|
|
),
|
|||
|
|
{ width: 1200, height: 630 }
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 📱 React Hook для удобства
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// hooks/useOgImage.ts
|
|||
|
|
import { useState } from 'react'
|
|||
|
|
|
|||
|
|
export function useOgImage() {
|
|||
|
|
const [loading, setLoading] = useState(false)
|
|||
|
|
|
|||
|
|
const generateAndUpload = async (title: string, backgroundImage?: string) => {
|
|||
|
|
setLoading(true)
|
|||
|
|
try {
|
|||
|
|
// Генерируем OG изображение
|
|||
|
|
const ogUrl = `/api/og?title=${encodeURIComponent(title)}${
|
|||
|
|
backgroundImage ? `&image=${encodeURIComponent(backgroundImage)}` : ''
|
|||
|
|
}`
|
|||
|
|
|
|||
|
|
const response = await fetch(ogUrl)
|
|||
|
|
const buffer = await response.arrayBuffer()
|
|||
|
|
|
|||
|
|
// Загружаем в Quoter
|
|||
|
|
const formData = new FormData()
|
|||
|
|
const blob = new Blob([buffer], { type: 'image/png' })
|
|||
|
|
formData.append('file', blob, `og-${Date.now()}.png`)
|
|||
|
|
|
|||
|
|
const uploadResponse = await fetch('/api/upload-to-quoter', {
|
|||
|
|
method: 'POST',
|
|||
|
|
body: formData,
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
return await uploadResponse.text()
|
|||
|
|
} finally {
|
|||
|
|
setLoading(false)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return { generateAndUpload, loading }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Использование в компоненте
|
|||
|
|
function MyComponent() {
|
|||
|
|
const { generateAndUpload, loading } = useOgImage()
|
|||
|
|
|
|||
|
|
const handleCreateOg = async () => {
|
|||
|
|
const quoterUrl = await generateAndUpload('My Post Title', '/existing-image.jpg')
|
|||
|
|
console.log('OG image uploaded to:', quoterUrl)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<button onClick={handleCreateOg} disabled={loading}>
|
|||
|
|
{loading ? 'Generating...' : 'Create OG Image'}
|
|||
|
|
</button>
|
|||
|
|
)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## ⚡ Production Tips
|
|||
|
|
|
|||
|
|
### Кэширование
|
|||
|
|
```typescript
|
|||
|
|
// Cache OG images for 24 hours
|
|||
|
|
export default function handler(req) {
|
|||
|
|
const response = new ImageResponse(/* ... */)
|
|||
|
|
|
|||
|
|
response.headers.set('Cache-Control', 'public, max-age=86400')
|
|||
|
|
response.headers.set('CDN-Cache-Control', 'public, max-age=86400')
|
|||
|
|
|
|||
|
|
return response
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Error Handling
|
|||
|
|
```typescript
|
|||
|
|
export default async function handler(req) {
|
|||
|
|
try {
|
|||
|
|
return new ImageResponse(/* ... */)
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('OG generation failed:', error)
|
|||
|
|
|
|||
|
|
// Fallback изображение
|
|||
|
|
return new Response('Failed to generate image', { status: 500 })
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Environment Variables
|
|||
|
|
```bash
|
|||
|
|
# .env.local
|
|||
|
|
QUOTER_API_URL=https://quoter.staging.discours.io
|
|||
|
|
QUOTER_AUTH_TOKEN=your_jwt_token
|
|||
|
|
NEXT_PUBLIC_OG_BASE_URL=https://yoursite.com/api/og
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🔗 Полезные ссылки
|
|||
|
|
|
|||
|
|
- [Полная документация по интеграции](./vercel-og-integration.md)
|
|||
|
|
- [Quoter API Reference](./api-reference.md)
|
|||
|
|
- [Vercel OG Official Docs](https://vercel.com/docs/concepts/functions/edge-functions/og-image-generation)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
💋 **Упрощение**: Один endpoint для генерации, один для загрузки - минимум кода, максимум результата!
|