Emne

Hurtig start — Generér dit første OG-billede

Denne FastOG-hurtigstart får dit første dynamiske Open Graph-billede live på få minutter. Tilmeld dig gratis — vælg derefter en Dynamic API-plan for render-kvote. Du opretter din API-nøgle og HMAC-hemmelighed efter behov i dashboardet. Hver render er ren — intet vandmærke på nogen plan, gratis tester inkluderet. Prøv den gratis OG-billedtester for at se pipeline i aktion, og signér derefter dine egne URL'er med trinene nedenfor.

Sådan fungerer det — Arkitektur

FastOG er bygget til on-demand OG-billeder: dit website signerer en URL ved side-renderingstidspunktet, crawleren henter den, FastOG renderer PNG'en én gang, og Cloudflare cacher den, så alle senere hentninger er øjeblikkelige og gratis.

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

De tre regler, der får det til at "bare fungere"

  1. Signér indholdet, ikke uret. Signaturen dækker alle parametre (titel, skabelon, …). Identiske parametre producerer altid den identiske URL — det er det, der får edge-cachen til at fungere: samme indhold → samme URL → cache-hit for evigt.
  2. Billedet er permanent efter den første rendering. Signaturen styrer kun den første rendering. Når et billede først er renderet, gemmer FastOGs edge det i et år med indholdsparametrene som nøgle — senere forespørgsler med de samme parametre serveres fra edge'en og rammer aldrig rendereren (tæller ikke mod dit kvota). En ugyldig signatur på cachelagret indhold returnerer stadig det cachelagrede PNG (svaret rapporterer, hvilket lag der svarede: X-FastOG-Worker-Cache: HIT eller R2); kun et cache-miss får 401.
  3. Den eneste fejltilstand. En manipuleret URL (nogen ændrer title/template) får 401 hos FastOG ved et cache-miss — så den kan aldrig bruges til at bruge dit kvota. Legitime URL'er udløber aldrig: de forbliver gyldige, indtil du roterer nøglens HMAC-hemmelighed.

1. Hent din API-nøgle og HMAC-hemmelighed

  1. Gå til FastOG-dashboard → API-nøgler

  2. Klik på "Opret ny API-nøgle"

  3. Giv den et navn (f.eks. "Produktionswebsted")

  4. Kopiér begge værdier (vises kun én gang!):

    • API-nøgle: sk_... (32 tegn efter præfikset)
    • HMAC-hemmelighed: 64-tegns hex-streng
  5. Gem dem i dit miljø:

env
# 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. Implementér URL-signeringsfunktionen

For komplette tutorials med flere frameworks og mønstre, se:

Node.js / Next.js

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

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

python
# 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. Brug i din blog/website

Next.js App Router

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

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

django
{# 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. Test din implementering

bash
# 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>"

Forventet:

  • ✅ Gyldig signatur → Returnerer OG-billede
  • ❌ Manipuleret parameter (f.eks. title ændret) → {"code": "invalid_signature"}
  • ❌ Ugyldig signatur → {"code": "invalid_signature"}

5. Sikkerhedstjekliste

  • [ ] HMAC-hemmelighed gemt i miljøvariabler (ikke i koden)
  • [ ] Brug af HTTPS til alle anmodninger
  • [ ] Hemmeligheden udsættes aldrig i JavaScript på klientsiden
  • [ ] Overvågning af fejlede signaturforsøg

6. Håndter fejl

javascript
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;
    }
}

Næste trin

  1. Implementér signering i din websides backend (se vejledningerne)
  2. Test med FastOG API'et
  3. Overvåg loggene for fejlede signeringsforsøg
  4. Opsæt advarsler for gentagne fejl

For fuld dokumentation, se HMAC-signaturvejledningen.

Fuld API-referencedokumentation

Prøv det gratis – ingen tilmelding nødvendig

Forhåndsvis og test dynamiske OG-billeder på få sekunder.

Åbn den gratis OG-billedtester

A BeCaSimulations site