# π 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(
(
{title}
),
{ 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
```
## π¨ Π Π°ΡΡΠΈΡΠ΅Π½Π½ΡΠΉ ΠΏΡΠΈΠΌΠ΅Ρ Ρ ΠΈΠ·ΠΎΠ±ΡΠ°ΠΆΠ΅Π½ΠΈΠ΅ΠΌ ΡΠΎΠ½Π°
```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(
(
{/* ΠΠ°ΡΠ΅ΠΌΠ½Π΅Π½ΠΈΠ΅ Π΄Π»Ρ ΡΠΈΡΠ°Π΅ΠΌΠΎΡΡΠΈ */}
{/* ΠΠ°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ */}
{title}
),
{ 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 (
)
}
```
## β‘ 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 Π΄Π»Ρ Π³Π΅Π½Π΅ΡΠ°ΡΠΈΠΈ, ΠΎΠ΄ΠΈΠ½ Π΄Π»Ρ Π·Π°Π³ΡΡΠ·ΠΊΠΈ - ΠΌΠΈΠ½ΠΈΠΌΡΠΌ ΠΊΠΎΠ΄Π°, ΠΌΠ°ΠΊΡΠΈΠΌΡΠΌ ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠ°!