### GET /api/og — Generate a dynamic OG image Source: https://context7.com/basementstudio/basement-context-vault/llms.txt This API route generates a dynamic Open Graph image. It accepts an optional `title` query parameter and returns a PNG image. The image is composed using a custom font and background, with automatic font-size scaling based on title length. On failure, it returns a plain-text error response with a 500 status code. ```APIDOC ## GET /api/og — Generate a dynamic OG image ### Description Edge Runtime API route that accepts an optional `title` query parameter and returns a PNG image. It loads a custom TTF font and a JPEG background from the `assets/` directory, composes the layout with `@vercel/og` (`ImageResponse`), and applies automatic font-size scaling based on title length. On any asset-loading or rendering failure it returns a `500` plain-text response. ### Method GET ### Endpoint /api/og #### Query Parameters - **title** (string) - Optional - The title to be displayed on the OG image. If not provided, a default title will be used. ### Request Example ```bash curl "https://your-domain.com/api/og?title=Hello%20World" ``` ### Response #### Success Response (200) - **Content-Type**: `image/png` - **Cache-Control**: `public, max-age=86400` #### Response Example (Returns a PNG image file) #### Error Response (500) - **Content-Type**: `text/plain` - **Body**: `Failed to generate the image` ``` -------------------------------- ### Configure Site Details for Metadata Source: https://github.com/basementstudio/basement-context-vault/blob/main/nextjs/dynamic-og/README.md Update the SITE_CONFIG object in `lib/metadata.ts` with your specific site's full name, URL, and default description for SEO. ```typescript const SITE_CONFIG = { fullName: "Your Company Name", // Your site/company name url: "https://yoursite.com", // Your domain description: "Your site description", // Default meta description } ``` -------------------------------- ### Create Metadata Utility for Dynamic OG Images Source: https://github.com/basementstudio/basement-context-vault/blob/main/nextjs/dynamic-og/README.md This utility function generates metadata for Next.js pages, including dynamic OG image URLs. Configure SITE_CONFIG with your site's details. ```typescript "use server"; import type { Metadata } from "next"; const SITE_CONFIG = { fullName: "Your Site Name", url: "https://yoursite.com", description: "Your site description for SEO." } export async function createMetadata({ title, ogTitle, description, image, pathname, canonical, }: { title?: string; ogTitle?: string; description?: string; image?: string; pathname?: string; canonical?: string; }): Promise { const metaDescription = description ?? SITE_CONFIG.description; const getOgTitle = () => { const _ogTitle = ogTitle ?? title; if (!_ogTitle) return ""; return `?title=${encodeURIComponent(_ogTitle)}`; }; const generatedOgImage = `${SITE_CONFIG.url}/api/og${getOgTitle()}`; const ogImage = image ?? generatedOgImage; const url = pathname ? `${SITE_CONFIG.url}${pathname.startsWith("/") ? "" : "/"}${pathname}` : SITE_CONFIG.url; return { metadataBase: new URL(SITE_CONFIG.url), title: title, description: metaDescription, keywords: [ "Your", "relevant", "keywords", "here" ], authors: [{ name: "Your Name" }], icons: { icon: [ { url: "/favicon.svg", sizes: "any", type: "image/svg+xml", }, ], }, manifest: "/site.webmanifest", applicationName: SITE_CONFIG.fullName, openGraph: { type: "website", locale: "en_US", url, title: title, description: metaDescription, siteName: SITE_CONFIG.fullName, images: [ { url: ogImage, width: 1200, height: 630, alt: title, }, ], }, twitter: { card: "summary_large_image", title: title, description: metaDescription, images: [ogImage], }, alternates: { canonical: canonical ?? "/", }, }; } ``` -------------------------------- ### Test OG Image Endpoint Directly Source: https://github.com/basementstudio/basement-context-vault/blob/main/nextjs/dynamic-og/README.md Access your OG image generation endpoint via a web browser to test its functionality and preview generated images. ```bash https://yoursite.com/api/og https://yoursite.com/api/og?title=Your%20Custom%20Title ``` -------------------------------- ### createMetadata Source: https://context7.com/basementstudio/basement-context-vault/llms.txt Generates a complete Next.js Metadata object, including Open Graph and Twitter card configurations. It automatically creates an OG image URL if one is not provided, pointing to the `/api/og` endpoint. ```APIDOC ## createMetadata ### Description Generate full Next.js page metadata with auto OG image. This Server Action accepts optional `title`, `ogTitle`, `description`, `image`, `pathname`, and `canonical` values and returns a complete Next.js `Metadata` object. When no custom `image` is provided, it automatically constructs the OG image URL pointing at `/api/og?title=`. ### Method Server Action (`"use server"`) ### Parameters - **title** (string) - Optional - The main title for the page. - **ogTitle** (string) - Optional - Overrides the title used in the OG image only. - **description** (string) - Optional - The page description. - **image** (string) - Optional - A custom image URL to skip auto-generation. - **pathname** (string) - Optional - The current page's pathname. - **canonical** (string) - Optional - The canonical URL for the page. ### Returns - **Promise** - A complete Next.js Metadata object. ### Request Example (Usage in a static page) ```typescript // app/about/page.tsx import { createMetadata } from "@/lib/metadata"; export const metadata = await createMetadata({ title: "About Us", description: "Learn more about Acme and our mission.", pathname: "/about", }); ``` ### Request Example (Usage in a dynamic route) ```typescript // app/blog/[slug]/page.tsx import { createMetadata } from "@/lib/metadata"; export async function generateMetadata({ params }: { params: { slug: string } }) { const post = await fetchPost(params.slug); // your data-fetching logic return createMetadata({ title: `${post.title} | Acme`, ogTitle: post.title, description: post.excerpt, pathname: `/blog/${params.slug}`, canonical: `/blog/${params.slug}`, }); } ``` ### Request Example (Usage with a custom image) ```typescript // Usage with a custom image (skips auto-generation) export const metadata = await createMetadata({ title: "Product Launch", image: "https://acme.com/images/launch-og.png", pathname: "/launch", }); ``` ``` -------------------------------- ### Generate Dynamic OG Image API Route Source: https://context7.com/basementstudio/basement-context-vault/llms.txt This Edge Runtime API route generates a PNG image with a dynamic title. It loads custom fonts and background images, composes the layout using @vercel/og, and applies responsive font sizing. Handles asset loading and rendering failures by returning a 500 error. ```typescript // app/api/og/route.tsx import type { NextRequest } from "next/server"; import { ImageResponse } from "next/og"; export const runtime = "edge"; const defaultTitle = "This is a captivating title crafted specifically for the Blog"; export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); const title = getFinalTitle(searchParams.get("title") || defaultTitle, 100); // Load assets in parallel for speed const [flautaFont, background] = await Promise.all([ fetch(new URL("./assets/fonts/flauta.ttf", import.meta.url)).then((res) => { if (!res.ok) throw new Error(`Failed to load font: ${res.status}`); return res.arrayBuffer(); }), fetch(new URL("./assets/images/background.jpg", import.meta.url)).then((res) => { if (!res.ok) throw new Error(`Failed to load background: ${res.status}`); return res.arrayBuffer(); }), ]); // Responsive font sizing: shorter titles get larger text const getFontSize = () => { const len = title.length; const scale = 0.7; if (len >= 90) return 60 * scale; if (len >= 80) return 64 * scale; if (len >= 60) return 72 * scale; if (len >= 40) return 80 * scale; if (len >= 30) return 88 * scale; if (len >= 25) return 92 * scale; if (len >= 20) return 110 * scale; if (len >= 10) return 120 * scale; return 153 * scale; }; return new ImageResponse( (
{title}
), { width: 1200, height: 642, fonts: [{ name: "Flauta", data: flautaFont, style: "normal" }], headers: { "Content-Type": "image/png", "Cache-Control": "public, max-age=86400", // cached for 24 hours }, } ); } catch (e: unknown) { console.error(e instanceof Error ? e.message : "Unknown OG generation error"); return new Response("Failed to generate the image", { status: 500 }); } } // Usage via curl: // curl "https://acme.com/api/og?title=Hello%20World" --output og.png // curl "https://acme.com/api/og" --output og-default.png ``` -------------------------------- ### Create Dynamic OG Image API Route Source: https://github.com/basementstudio/basement-context-vault/blob/main/nextjs/dynamic-og/README.md This Next.js API route generates dynamic OG images. It fetches a title from search parameters, loads custom fonts and background images, and dynamically adjusts font size based on title length. Ensure font and image assets are placed in the specified paths. ```typescript import type { NextRequest } from "next/server"; import { ImageResponse } from "next/og"; export const runtime = "edge"; const defaultTitle = "Your Default Title Here"; export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); const title = getFinalTitle(searchParams.get("title") || defaultTitle, 100); // Load custom font const flautaFont = await fetch( new URL("./assets/fonts/flauta.ttf", import.meta.url) ).then((res) => { if (!res.ok) { throw new Error(`Failed to load font: ${res.status}`); } return res.arrayBuffer(); }); // Load background image const background = await fetch( new URL("./assets/images/background.jpg", import.meta.url) ).then((res) => { if (!res.ok) { throw new Error(`Failed to load background image: ${res.status}`); } return res.arrayBuffer(); }); // Dynamic font sizing based on title length const getFontSize = () => { const length = title.length; const fontScaleFactor = 0.7; switch (true) { case length >= 90: return 60 * fontScaleFactor; case length >= 80: return 64 * fontScaleFactor; case length >= 60: return 72 * fontScaleFactor; case length >= 40: return 80 * fontScaleFactor; case length >= 30: return 88 * fontScaleFactor; case length >= 25: return 92 * fontScaleFactor; case length >= 20: return 110 * fontScaleFactor; case length >= 10: return 120 * fontScaleFactor; default: return 153 * fontScaleFactor; } }; return new ImageResponse( (
{title}
), { width: 1200, height: 642, fonts: [ { name: "Flauta", data: flautaFont, style: "normal", }, ], headers: { "Content-Type": "image/png", "Cache-Control": "public, max-age=86400", }, } ); } catch (e: unknown) { if (e instanceof Error) { console.error(`Failed to generate OG image: ${e.message}`); } else { console.error("An unknown error occurred while generating OG image"); } return new Response("Failed to generate the image", { status: 500, }); } } const getFinalTitle = (str: string | null | undefined, limit: number) => { if (!str) return defaultTitle; // Remove site name suffix and trim whitespace const cleanStr = str.replace(/\s*\|\s*YourSiteName$/, "").trim(); return cleanStr.length > limit ? `${cleanStr.slice(0, limit)}...` : cleanStr; }; ``` -------------------------------- ### Use Metadata Utility in Page Component Source: https://github.com/basementstudio/basement-context-vault/blob/main/nextjs/dynamic-og/README.md Import and use the `createMetadata` function within your Next.js page's `generateMetadata` function to set dynamic page titles, descriptions, and OG image paths. ```typescript // app/blog/[slug]/page.tsx import { createMetadata } from "@/lib/metadata"; export async function generateMetadata({ params }: { params: { slug: string } }) { const post = await getPost(params.slug); return createMetadata({ title: post.title, description: post.excerpt, pathname: `/blog/${params.slug}`, }); } export default function BlogPost({ params }: { params: { slug: string } }) { // Your component } ``` -------------------------------- ### Generate Next.js Metadata with createMetadata Source: https://context7.com/basementstudio/basement-context-vault/llms.txt The `createMetadata` Server Action generates a complete Next.js `Metadata` object. It automatically creates an OG image URL using the `/api/og` endpoint if no custom image is provided. This function is useful for setting SEO-friendly metadata across your application. ```typescript "use server"; import type { Metadata } from "next"; const SITE_CONFIG = { fullName: "Acme", url: "https://acme.com", description: "Acme is a company that makes widgets.", }; export async function createMetadata({ title, ogTitle, description, image, pathname, canonical, }: { title?: string; ogTitle?: string; // override the title used in the OG image only description?: string; image?: string; // supply a custom image URL to skip auto-generation pathname?: string; canonical?: string; }): Promise { const metaDescription = description ?? SITE_CONFIG.description; const getOgTitle = () => { const _ogTitle = ogTitle ?? title; if (!_ogTitle) return ""; return `?title=${encodeURIComponent(_ogTitle)}`; }; const generatedOgImage = `${SITE_CONFIG.url}/api/og${getOgTitle()}`; const ogImage = image ?? generatedOgImage; const url = pathname ? `${SITE_CONFIG.url}${pathname.startsWith("/") ? "" : "/"}${pathname}` : SITE_CONFIG.url; return { metadataBase: new URL(SITE_CONFIG.url), title, description: metaDescription, openGraph: { type: "website", locale: "en_US", url, title, description: metaDescription, siteName: SITE_CONFIG.fullName, images: [{ url: ogImage, width: 1200, height: 630, alt: title }], }, twitter: { card: "summary_large_image", title, description: metaDescription, images: [ogImage], }, alternates: { canonical: canonical ?? "/" }, }; } ``` ```typescript // ── Usage in a static page ────────────────────────────────────────────────── // app/about/page.tsx import { createMetadata } from "@/lib/metadata"; export const metadata = await createMetadata({ title: "About Us", description: "Learn more about Acme and our mission.", pathname: "/about", }); // openGraph.images[0].url → "https://acme.com/api/og?title=About%20Us" ``` ```typescript // ── Usage in a dynamic route ──────────────────────────────────────────────── // app/blog/[slug]/page.tsx import { createMetadata } from "@/lib/metadata"; export async function generateMetadata({ params }: { params: { slug: string } }) { const post = await fetchPost(params.slug); // your data-fetching logic return createMetadata({ title: `${post.title} | Acme`, // full tag ogTitle: post.title, // clean title in the image description: post.excerpt, pathname: `/blog/${params.slug}`, canonical: `/blog/${params.slug}`, }); } // openGraph.images[0].url → "https://acme.com/api/og?title=My%20Post%20Title" ``` ```typescript // ── Usage with a custom image (skips auto-generation) ─────────────────────── export const metadata = await createMetadata({ title: "Product Launch", image: "https://acme.com/images/launch-og.png", // static override pathname: "/launch", }); ``` -------------------------------- ### Metadata Generation Utility Source: https://github.com/basementstudio/basement-context-vault/blob/main/nextjs/dynamic-og/README.md Utility function to generate metadata for OG images, including text styling and layout. ```tsx import { ImageResponseOptions, ImageResponse } from "next/server"; interface MetadataOptions { title: string; } export async function getMetadata({ title, }: MetadataOptions): Promise<ImageResponseOptions> { return ( <div style={{ height: "100%", width: "100%", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", backgroundImage: "url(https://nextjs.dynamic-og.vercel.app/og/background.jpg)", backgroundSize: "100% 100%", fontSize: 70, fontWeight: "bold", color: "#fff", fontFamily: "Flauta", }} > <p style={{ margin: 0, padding: "0 100px", textAlign: "center" }}>{title}</p> </div> ); } ``` -------------------------------- ### OG Image Generation Endpoint Source: https://github.com/basementstudio/basement-context-vault/blob/main/nextjs/dynamic-og/README.md This is the main endpoint for generating OG images. It handles dynamic content and styling. ```tsx import { ImageResponse } from "next/server"; import { getMetadata } from "@/lib/metadata"; export const runtime = "edge"; export async function GET(request: Request) { const { searchParams } = new URL(request.url); const title = searchParams.get("title") ?? "Default Title"; const metadata = await getMetadata({ title, }); return new ImageResponse(metadata, { width: 1200, height: 630, fonts: [ { name: "Flauta", data: await fetch( new URL("../../assets/fonts/flauta.ttf", import.meta.url) ).then((res) => res.arrayBuffer()), style: "normal", }, ], }); } ``` -------------------------------- ### Debug Mode Console Logs Source: https://github.com/basementstudio/basement-context-vault/blob/main/nextjs/dynamic-og/README.md Add these console logs to your TypeScript code to help debug issues with dynamic OG image generation. They help verify if variables like title, and fonts/backgrounds are loaded correctly. ```typescript console.log('Title:', title); console.log('Font loaded:', !!flautaFont); console.log('Background loaded:', !!background); ``` -------------------------------- ### getFinalTitle Source: https://context7.com/basementstudio/basement-context-vault/llms.txt Sanitizes and truncates a page title by stripping a trailing site-name suffix, trimming whitespace, and truncating to a specified character limit. This is a private helper function. ```APIDOC ## getFinalTitle ### Description Sanitize and truncate a page title. This private helper function strips a trailing site-name suffix (e.g., `" | Acme"`) from the raw `<title>` value, trims whitespace, and truncates to a configurable character limit. ### Parameters - **str** (string | null | undefined) - The raw title string to sanitize. - **limit** (number) - The maximum character limit for the truncated title. ### Returns - **string** - The sanitized and potentially truncated title. ### Examples ```typescript getFinalTitle("My Blog Post | Acme", 100); // → "My Blog Post" getFinalTitle("A very long title that exceeds the limit set for the OG image canvas...", 30); // → "A very long title that exceeds..." getFinalTitle(null, 100); // → defaultTitle (fallback) getFinalTitle("", 100); // → defaultTitle (fallback) ``` ``` -------------------------------- ### Sanitize and Truncate Page Title with getFinalTitle Source: https://context7.com/basementstudio/basement-context-vault/llms.txt Use `getFinalTitle` to clean raw page titles by removing a trailing site name suffix, trimming whitespace, and truncating to a specified character limit. This prevents text overflow in OG images. It handles null, undefined, or empty strings by returning a default title. ```typescript const getFinalTitle = (str: string | null | undefined, limit: number): string => { if (!str) return defaultTitle; // Strip trailing " | SiteName" pattern produced by Next.js title templates const cleanStr = str.replace(/\s*\|\s*Acme$/, "").trim(); return cleanStr.length > limit ? `${cleanStr.slice(0, limit)}...` : cleanStr; }; ``` ```typescript // Examples getFinalTitle("My Blog Post | Acme", 100); // → "My Blog Post" getFinalTitle("A very long title that exceeds the limit set for the OG image canvas...", 30); // → "A very long title that exceeds..." getFinalTitle(null, 100); // → defaultTitle (fallback) getFinalTitle("", 100); // → defaultTitle (fallback) ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.