Быстрый старт — создайте своё первое OG-изображение
Этот краткий гайд по FastOG поможет вам запустить первое динамическое изображение Open Graph за несколько минут. Регистрация бесплатна — затем выберите тариф Dynamic API для квоты на рендеринг. Вы создаёте свой API-ключ и HMAC-секрет по мере необходимости в панели управления. Каждый рендер чистый — без водяных знаков на любом тарифе, включая бесплатный тестовый режим. Попробуйте бесплатный тестер OG-изображений, чтобы увидеть конвейер в действии, а затем подпишите свои собственные URL-адреса, следуя шагам ниже.
Как это работает — архитектура
FastOG создан для OG-изображений по требованию: ваш сайт подписывает URL в момент рендеринга страницы, краулер запрашивает его, FastOG один раз рендерит PNG, а Cloudflare кэширует его, поэтому каждый последующий запрос мгновенный и бесплатный.
sequenceDiagram
participant You as You share a link
participant Crawler as Social crawler
participant Site as Your website
participant FastOG as FastOG API
participant Edge as Edge cache
You->>Crawler: share a link to your post
Crawler->>Site: GET the page without JavaScript
Site->>Site: sign the og:image URL at render time
Site-->>Crawler: HTML with signed og:image URL
Crawler->>Edge: fetch the og:image URL
Edge->>FastOG: cache miss, first time
FastOG->>FastOG: verify the HMAC covers the exact params
FastOG-->>Edge: render PNG, counts 1 toward monthly quota, cached for 1 year
Edge-->>Crawler: PNG
Crawler-->>You: image shown in the post preview
Note over Edge, Crawler: identical params and URL mean a cache hit with no render
Три правила, благодаря которым это «просто работает»
- Подписывайте контент, а не время. Подпись покрывает все параметры (заголовок, шаблон и т. д.). Одинаковые параметры всегда дают идентичный URL — именно это обеспечивает работу пограничного кэша: одинаковый контент → одинаковый URL → попадание в кэш навсегда.
- Изображение постоянно после первого рендеринга. Подпись ограничивает только первый рендеринг. После того как изображение отрендерено, пограничный слой FastOG хранит его в течение года, ключом служат параметры контента — последующие запросы с теми же параметрами обслуживаются с пограничного слоя и никогда не попадают в рендерер (не учитываются в вашей квоте). Неверная подпись на кэшированном контенте всё равно возвращает кэшированный PNG (в ответе указывается, какой уровень ответил:
X-FastOG-Worker-Cache: HITилиR2); только при промахе кэша возвращается 401. - Единственный сценарий отказа. Подделанный URL (кто-то изменил
title/template) получает 401 от FastOG при промахе кэша — поэтому его нельзя использовать для расходования вашей квоты. Легитимные URL никогда не истекают: они остаются действительными, пока вы не ротируете HMAC-секрет ключа.
1. Получите свой API-ключ и HMAC-секрет
-
Перейдите в FastOG Dashboard → API Keys
-
Нажмите «Create New API Key»
-
Присвойте ему имя (например, «Production Website»)
-
Скопируйте оба значения (показываются только один раз!):
- API Key:
sk_...(32 символа после префикса) - HMAC Secret: 64-символьная шестнадцатеричная строка
- API Key:
-
Сохраните их в переменных окружения:
# Your website backend .env
FASTOG_API_KEY=sk_your_full_api_key
FASTOG_HMAC_SECRET=a1b2c3d4e5f6... # 64 chars
FASTOG_API_URL=https://fastog.com/api/v1/og
2. Реализация функции подписи URL
Полные руководства с несколькими фреймворками и паттернами см. в:
- Next.js App Router
- Next.js Pages Router
- React
- SvelteKit
- Vue / Nuxt
- Astro
- Express
- Vanilla Node.js
- JavaScript
- PHP / Laravel
- Python
- Ruby / Rails
Node.js / Next.js
// lib/fastog-signer.js
import crypto from "crypto";
// RFC-3986 encode (encodeURIComponent leaves !'()* unencoded).
const enc = (s) =>
encodeURIComponent(s).replace(
/[!'()*]/g,
(c) => "%" + c.charCodeAt(0).toString(16).toUpperCase(),
);
export function generateOgImageUrl(params) {
const baseUrl = process.env.FASTOG_API_URL;
const apiKey = process.env.FASTOG_API_KEY;
const hmacSecret = process.env.FASTOG_HMAC_SECRET;
const all = { ...params, key: apiKey };
// Canonical: every param (except s), sorted, RFC-3986 encoded, joined with "&".
const pairs = [];
for (const [k, v] of Object.entries(all)) {
for (const vv of Array.isArray(v) ? v : [v])
pairs.push(`${enc(k)}=${enc(vv)}`);
}
pairs.sort();
const canonical = pairs.join("&");
// Content-signed: message = path + canonical query. No timestamp.
const signature = crypto
.createHmac("sha256", hmacSecret)
.update(`GET/api/v1/og\n${canonical}`)
.digest("hex");
const url = new URL(baseUrl);
for (const [k, v] of Object.entries(all)) {
for (const vv of Array.isArray(v) ? v : [v])
url.searchParams.append(k, vv);
}
url.searchParams.set("s", signature);
return url.toString();
}
PHP (Laravel)
// app/Services/QuickogSigner.php
function generateOgImageUrl(array $params): string
{
$baseUrl = config('services.fastog.api_url', 'https://fastog.com/api/v1/og');
$apiKey = config('services.fastog.api_key');
$hmacSecret = config('services.fastog.hmac_secret');
$all = array_merge($params, ['key' => $apiKey]);
// Canonical: every param except s, keys sorted (array values sorted too),
// RFC-3986 encoded, joined with '&'. Content-signed — no timestamp.
$pairs = [];
foreach ($all as $k => $v) {
$values = is_array($v) ? $v : [$v];
sort($values, SORT_STRING);
foreach ($values as $vv) {
$pairs[] = rawurlencode((string) $k) . '=' . rawurlencode((string) $vv);
}
}
sort($pairs, SORT_STRING);
$canonical = implode('&', $pairs);
$signature = hash_hmac('sha256', 'GET/api/v1/og' . "\n" . $canonical, $hmacSecret);
return $baseUrl . '?' . http_build_query(array_merge($all, ['s' => $signature]));
}
Python (Django/FastAPI)
# utils/fastog.py
import hmac
import hashlib
import os
from urllib.parse import quote, urlencode
def _enc(s):
return quote(str(s), safe='-._~') # RFC-3986: only unreserved chars
def generate_og_image_url(params: dict) -> str:
base_url = os.environ['FASTOG_API_URL']
api_key = os.environ['FASTOG_API_KEY']
hmac_secret = os.environ['FASTOG_HMAC_SECRET']
all_params = {**params, 'key': api_key}
# Canonical: every param except s, sorted, RFC-3986 encoded, joined with '&'.
pairs = []
for k, v in all_params.items():
for vv in sorted(v if isinstance(v, list) else [v]):
pairs.append(f"{_enc(k)}={_enc(vv)}")
canonical = '&'.join(sorted(pairs))
# Content-signed: message = path + canonical query. No timestamp.
signature = hmac.new(
hmac_secret.encode(),
f"GET/api/v1/og\n{canonical}".encode(),
hashlib.sha256,
).hexdigest()
return f"{base_url}?{urlencode({**all_params, 's': signature})}"
3. Использование в вашем блоге или на сайте
Next.js App Router
// app/blog/[slug]/page.tsx
import { generateOgImageUrl } from "@/lib/fastog-signer";
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
const ogImageUrl = generateOgImageUrl({
template: "blog",
title: post.title,
subtitle: post.excerpt,
author: post.author.name,
});
return {
title: post.title,
description: post.excerpt,
openGraph: {
images: [ogImageUrl],
},
};
}
Laravel Blade
{{-- resources/views/blog/show.blade.php --}}
@php
$ogImageUrl = generateOgImageUrl([
'template' => 'blog',
'title' => $post->title,
'subtitle' => $post->excerpt,
'author' => $post->author->name,
]);
@endphp
<head>
<meta property="og:image" content="{{ $ogImageUrl }}" />
<meta property="og:title" content="{{ $post->title }}" />
<meta property="og:description" content="{{ $post->excerpt }}" />
</head>
Django Template
{# blog/post_detail.html #}
{% load fastog_tags %}
<head>
<meta property="og:image" content="{% generate_og_image_url post %}" />
<meta property="og:title" content="{{ post.title }}" />
<meta property="og:description" content="{{ post.excerpt }}" />
</head>
4. Проверьте вашу реализацию
# Generate a test URL
node -e "
const { generateOgImageUrl } = require('./lib/fastog-signer');
console.log(generateOgImageUrl({ title: 'Test', template: 'default' }));
"
# Test with curl
curl "https://fastog.com/api/v1/og?title=Test&key=sk_quick_123abc&s=<sig>"
Ожидается:
- ✅ Действительная подпись → Возвращается OG-изображение
- ❌ Изменённый параметр (например,
titleизменён) →{"code": "invalid_signature"} - ❌ Недействительная подпись →
{"code": "invalid_signature"}
5. Чек-лист безопасности
- [ ] Секрет HMAC хранится в переменных окружения (не в коде)
- [ ] Используется HTTPS для всех запросов
- [ ] Секрет никогда не раскрывается в клиентском JavaScript
- [ ] Ведется мониторинг неудачных попыток подписи
6. Обработка ошибок
if (response.status === 401) {
const error = await response.json();
switch (error.code) {
case "missing_signature":
// URL is missing `key` or `s` — re-generate it with your signer.
break;
case "invalid_signature":
// A param changed after signing, or the secret doesn't match.
// Re-sign the URL after any content change; verify the secret on both sides.
break;
}
}
Дальнейшие шаги
- Реализуйте подпись в бэкенде вашего сайта (см. руководства)
- Протестируйте с помощью FastOG API
- Отслеживайте журналы на предмет неудачных попыток подписи
- Настройте оповещения о повторяющихся сбоях
Полную документацию см. в Руководстве по HMAC-подписи.