Εικόνες OG στο SvelteKit
Δημιουργήστε εικόνες OG στο SvelteKit με το FastOG API — με συνδρομή Dynamic API (δημιουργήστε το κλειδί API σας στον πίνακα ελέγχου), βάσει συνδρομής, με ασφάλεια HMAC. Αυτός ο οδηγός σάς δείχνει πώς να δημιουργήσετε ένα βοηθητικό εργαλείο υπογραφής (signer utility) για διευθύνσεις URL εικόνων OG με υπογραφή HMAC και να αποδίδετε δυναμικές εικόνες Open Graph εν κινήσει. Χρησιμοποιήστε το Δωρεάν Εργαλείο Δοκιμής OG για προεπισκόπηση πριν από την ενσωμάτωση.
Γρήγορη εκκίνηση
1. Δημιουργήστε το βοηθητικό εργαλείο υπογραφής (signer utility)
// src/lib/og-image-signer.js
import crypto from "node:crypto";
export function generateSignedOgUrl(params, options = {}) {
const {
baseUrl = "https://fastog.com/api/v1/og",
apiKey = process.env.FASTOG_API_KEY,
hmacSecret = process.env.FASTOG_HMAC_SECRET,
} = options;
if (!apiKey || !hmacSecret) {
throw new Error("Missing FASTOG_API_KEY or FASTOG_HMAC_SECRET");
}
// RFC-3986 percent-encoding: keep A-Za-z0-9-._~
const enc = (s) =>
encodeURIComponent(s).replace(
/[!'()*]/g,
(c) => "%" + c.charCodeAt(0).toString(16).toUpperCase(),
);
// Every query param except the signature, INCLUDING key
const all = { ...params, key: apiKey };
// Canonical query: sorted enc(key)=enc(value) pairs 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("&");
// Sign the exact bytes the server will verify
const signature = crypto
.createHmac("sha256", hmacSecret)
.update(`GET/api/v1/og\n${canonical}`)
.digest("hex");
// Build the URL from the same pairs so bytes match the canonical form
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(Array.isArray(v) ? `${k}[]` : k, vv);
}
}
url.searchParams.set("s", signature);
return url.toString();
}
2. Μεταβλητές περιβάλλοντος
# .env
FASTOG_API_KEY=sk_your_full_api_key_here
FASTOG_HMAC_SECRET=your_64_char_hex_secret_here
Σελίδα με Μεταδεδομένα
<!-- src/routes/blog/[slug]/+page.svelte -->
<script>
export let data;
$: ({ post, ogImageUrl } = data);
</script>
<svelte:head>
<title>{post.title}</title>
<meta property="og:image" content={ogImageUrl} />
<meta property="og:title" content={post.title} />
<meta property="og:description" content={post.excerpt} />
</svelte:head>
<article>
<h1>{post.title}</h1>
<p>{post.excerpt}</p>
</article>
// src/routes/blog/[slug]/+page.server.js
import { generateSignedOgUrl } from "$lib/og-image-signer";
export async function load({ params }) {
const post = await getPost(params.slug);
const ogImageUrl = generateSignedOgUrl({
template: "blog",
title: post.title,
subtitle: post.excerpt,
author: post.author.name,
});
return {
post,
ogImageUrl,
};
}
Διαδρομή API
// src/routes/api/og/+server.js
import { json } from "@sveltejs/kit";
import { generateSignedOgUrl } from "$lib/og-image-signer";
export async function GET({ url }) {
const title = url.searchParams.get("title");
const template = url.searchParams.get("template") || "default";
if (!title) {
return json({ error: "Missing title" }, { status: 400 });
}
const ogImageUrl = generateSignedOgUrl({
template,
title,
});
return json({ ogImageUrl });
}
Παράδειγμα Ηλεκτρονικού Εμπορίου
// src/routes/products/[id]/+page.server.js
import { generateSignedOgUrl } from "$lib/og-image-signer";
export async function load({ params }) {
const product = await getProduct(params.id);
const ogImageUrl = generateSignedOgUrl({
template: "ecommerce",
title: product.name,
price: product.price,
image: product.image,
});
return { product, ogImageUrl };
}
<!-- src/routes/products/[id]/+page.svelte -->
<script>
export let data;
$: ({ product, ogImageUrl } = data);
</script>
<svelte:head>
<meta property="og:image" content={ogImageUrl} />
<meta property="og:title" content={product.name} />
</svelte:head>
<main>
<h1>{product.name}</h1>
<p>{product.price}</p>
</main>
Προφίλ Χρήστη (Πρότυπο Ιστολογίου)
Το FastOG δεν διαθέτει πρότυπο profile — χρησιμοποιήστε το blog για μια κάρτα τύπου προφίλ, περνώντας το όνομα του χρήστη ως title, τη βιογραφία ως subtitle και το όνομα ως author.
// src/routes/users/[username]/+page.server.js
import { generateSignedOgUrl } from "$lib/og-image-signer";
export async function load({ params }) {
const user = await getUser(params.username);
const ogImageUrl = generateSignedOgUrl({
template: "blog",
title: user.name,
subtitle: user.bio,
author: user.name,
});
return { user, ogImageUrl };
}
Στοιχείο με Δυναμική Εικόνα OG
<!-- src/lib/ShareButtons.svelte -->
<script>
import { onMount } from "svelte";
export let post;
let ogImageUrl = null;
onMount(async () => {
const res = await fetch(`/api/og?title=${encodeURIComponent(post.title)}`);
const data = await res.json();
ogImageUrl = data.ogImageUrl;
});
</script>
<div class="share-buttons">
<button on:click={() => shareToTwitter(ogImageUrl)}>
Share on Twitter
</button>
</div>
Συνηθισμένα Λάθη
Υπογραφή από την Πλευρά του Πελάτη
Ποτέ μην υπογράφετε OG διευθύνσεις URL σε κώδικα προγράμματος περιήγησης — το μυστικό HMAC θα εκτεθεί.
❌ Λάθος:
<script>
import { generateSignedOgUrl } from "$lib/og-image-signer";
// This runs in the browser!
const ogUrl = generateSignedOgUrl({ title: post.title });
</script>
✅ Σωστό:
// +page.server.js (runs on server only)
import { generateSignedOgUrl } from "$lib/og-image-signer";
export async function load({ params }) {
const ogImageUrl = generateSignedOgUrl({ title: post.title });
return { ogImageUrl };
}
Υπογραφή Διαφορετικών Bytes από Αυτά που Στέλνετε
Η υπογραφή καλύπτει το GET/api/v1/og συν μια νέα γραμμή συν το κανονικό ερώτημα — τα ταξινομημένα ζεύγη enc(key)=enc(value) κάθε παραμέτρου εκτός από το s, συμπεριλαμβανομένου του key. Δημιουργήστε την τελική διεύθυνση URL από αυτά τα ίδια κωδικοποιημένα ζεύγη, διαφορετικά ο διακομιστής επιστρέφει 401 invalid_signature.