Files
quoter/docs/vercel-og-quickstart.md
Untone 7497b8c426
Some checks failed
Deploy / deploy (push) Has been skipped
CI / test (push) Failing after 20s
CI / lint (push) Successful in 7m1s
build-reconfig2
2025-09-02 10:46:51 +03:00

6.1 KiB
Raw Blame History

🚀 Quick Start: @vercel/og + Quoter

5-минутная настройка

1. Установка зависимостей

npm install @vercel/og

2. Создание API endpoint

// 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

// 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. Использование

// Генерация 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} />

🎨 Расширенный пример с изображением фона

// 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 для удобства

// 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

Кэширование

// 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

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

# .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

🔗 Полезные ссылки


💋 Упрощение: Один endpoint для генерации, один для загрузки - минимум кода, максимум результата!