Chủ đề

OG Images trong Next.js Pages Router

Tạo OG images trong Next.js Pages Router bằng Dynamic API dựa trên đăng ký của FastOG — với gói đăng ký Dynamic API (tạo khóa API của bạn trong bảng điều khiển). Sử dụng Free OG Image Tester để xem trước bất kỳ mẫu nào, sau đó kết nối các hình ảnh Open Graph động với URL được ký HMAC trong các route Pages Router của bạn.

Thiết lập

1. Tạo tiện ích ký (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. Biến môi trường

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

Tạo Trang Tĩnh (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,
    };
}

Kết xuất phía máy chủ (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 Route Proxy

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

Các Lỗi Thường Gặp

Sai Định Dạng Tin Nhắn

Tin nhắn được ký là GET/api/v1/og cộng với một ký tự xuống dòng cộng với chuỗi truy vấn chuẩn hóa (canonical query).

❌ Sai: GET/api/v1/og + path + apiKey được nối với nhau (không có chuỗi truy vấn chuẩn hóa) ✅ Đúng: GET/api/v1/og + \n + các cặp enc(key)=enc(value) chuẩn hóa đã được sắp xếp (bao gồm cả key)

Ký Các Byte Khác Với Những Gì Bạn Gửi

Xây dựng URL cuối cùng từ cùng các cặp enc(key)=enc(value) mà bạn ký, nếu không máy chủ sẽ trả về 401 invalid_signature.

Tài liệu tham khảo API đầy đủ

Dùng thử miễn phí — không cần đăng ký

Xem trước và kiểm tra hình ảnh OG động trong vài giây.

Mở Trình kiểm tra hình ảnh OG miễn phí

A BeCaSimulations site