หัวข้อ

OG Images ใน Next.js Pages Router

สร้าง OG images ใน Next.js Pages Router ด้วย Dynamic API แบบสมัครสมาชิกของ FastOG — โดยต้องมีการสมัครสมาชิก Dynamic API (สร้าง API key ของคุณในแดชบอร์ด) ใช้ Free OG Image Tester เพื่อดูตัวอย่างเทมเพลตใดก็ได้ จากนั้นเชื่อมต่อ OG images แบบไดนามิกด้วย URL ที่ลงนามด้วย HMAC ในเส้นทาง Pages Router ของคุณ

การตั้งค่า

1. สร้างยูทิลิตี้ตัวเซ็นชื่อ (signer utility)

javascript
// lib/og-image-signer.js
import crypto from "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. ตัวแปรสภาพแวดล้อม (environment variables)

bash
# .env.local
FASTOG_API_KEY=sk_your_full_api_key_here
FASTOG_HMAC_SECRET=your_64_char_hex_secret_here

การสร้างเว็บไซต์แบบคงที่ (Static Site Generation - SSG)

typescript
// pages/blog/[slug].tsx
import Head from "next/head";
import { generateSignedOgUrl } from "@/lib/og-image-signer";

export default function BlogPost({ post, ogImageUrl }) {
    return (
        <>
            <Head>
                <meta property="og:image" content={ogImageUrl} />
                <meta property="og:title" content={post.title} />
                <meta property="og:description" content={post.excerpt} />
            </Head>
            <article>
                <h1>{post.title}</h1>
                <p>{post.excerpt}</p>
            </article>
        </>
    );
}

export async function getStaticPaths() {
    const posts = await getAllPosts();
    return {
        paths: posts.map((post) => ({ params: { slug: post.slug } })),
        fallback: "blocking",
    };
}

export async function getStaticProps({ params }) {
    const post = await getPost(params.slug);

    const ogImageUrl = generateSignedOgUrl({
        template: "blog",
        title: post.title,
        subtitle: post.excerpt,
    });

    return {
        props: {
            post,
            ogImageUrl,
        },
        revalidate: 3600,
    };
}

การเรนเดอร์ฝั่งเซิร์ฟเวอร์ (Server-Side Rendering, SSR)

typescript
// pages/products/[id].tsx
import Head from "next/head";
import { generateSignedOgUrl } from "@/lib/og-image-signer";

export default function Product({ product, ogImageUrl }) {
    return (
        <>
            <Head>
                <meta property="og:image" content={ogImageUrl} />
                <meta property="og:title" content={product.name} />
            </Head>
            <main>
                <h1>{product.name}</h1>
                <p>{product.price}</p>
            </main>
        </>
    );
}

export async function getServerSideProps({ params }) {
    const product = await getProduct(params.id);

    const ogImageUrl = generateSignedOgUrl({
        template: "ecommerce",
        title: product.name,
        price: product.price,
        image: product.image,
    });

    return {
        props: {
            product,
            ogImageUrl,
        },
    };
}

ตัวพร็อกซีเส้นทาง API

typescript
// pages/api/og.ts
import type { NextApiRequest, NextApiResponse } from "next";
import { generateSignedOgUrl } from "@/lib/og-image-signer";

export default function handler(req: NextApiRequest, res: NextApiResponse) {
    const { title, template = "default" } = req.query;

    if (!title) {
        return res.status(400).json({ error: "Missing title" });
    }

    const ogImageUrl = generateSignedOgUrl({
        template: template as string,
        title: title as string,
    });

    res.json({ ogImageUrl });
}

ข้อผิดพลาดที่พบบ่อย

รูปแบบข้อความที่ไม่ถูกต้อง

ข้อความที่ลงนามคือ GET/api/v1/og บวกกับอักขระขึ้นบรรทัดใหม่ (newline) บวกกับคิวรีแบบ canonical

❌ ผิด: GET/api/v1/og + path + apiKey ต่อกัน (ไม่มีคิวรีแบบ canonical) ✅ ถูก: GET/api/v1/og + \n + คู่ enc(key)=enc(value) แบบ canonical ที่เรียงลำดับแล้ว (รวมถึง key)

ลงนามในไบต์ที่แตกต่างจากที่คุณส่ง

สร้าง URL สุดท้ายจากคู่ enc(key)=enc(value) ชุดเดียวกันกับที่คุณลงนาม มิฉะนั้นเซิร์ฟเวอร์จะคืนค่า 401 invalid_signature

เอกสารอ้างอิง API ฉบับเต็ม

ลองใช้ฟรี — ไม่ต้องสมัครสมาชิก

ดูตัวอย่างและทดสอบภาพ OG แบบไดนามิกได้ในไม่กี่วินาที

เปิดตัวทดสอบภาพ OG ฟรี

A BeCaSimulations site