@djangocfg/nextjs 2.1.51 → 2.1.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -440,7 +440,7 @@ function generateOgImageUrl(params, options = {}) {
440
440
  });
441
441
  const jsonString = JSON.stringify(cleanParams);
442
442
  const base64Data = encodeBase64(jsonString);
443
- return `${baseUrl}/${base64Data}`;
443
+ return `${baseUrl}/${base64Data}/`;
444
444
  } else {
445
445
  const searchParams = new URLSearchParams();
446
446
  Object.entries(params).forEach(([key, value]) => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/og-image/route.tsx","../../src/og-image/components/DefaultTemplate.tsx","../../src/og-image/utils/fonts.ts","../../src/og-image/utils/url.ts","../../src/og-image/utils/metadata.ts"],"sourcesContent":["/**\n * OG Image Route Handler\n *\n * Factory function to create OG Image route handler for Next.js App Router\n *\n * Usage:\n * ```tsx\n * // app/api/og/route.tsx\n * import { createOgImageHandler } from '@djangocfg/nextjs/og-image';\n * import { MyTemplate } from './templates';\n *\n * export const { GET, runtime } = createOgImageHandler({\n * template: MyTemplate,\n * defaultProps: {\n * siteName: 'My Site',\n * },\n * fonts: [{ family: 'Manrope', weight: 700 }],\n * });\n * ```\n */\n\nimport { ImageResponse } from 'next/og';\nimport { NextRequest } from 'next/server';\n\nimport { DefaultTemplate } from './components/DefaultTemplate';\nimport { loadGoogleFonts } from './utils';\nimport { parseOgImageData } from './utils/url';\n\nimport type { ReactElement } from 'react';\nimport type { OgImageTemplateProps } from './types';\n\nexport interface OgImageHandlerConfig {\n /** Custom template component (optional, defaults to DefaultTemplate) */\n template?: (props: OgImageTemplateProps) => ReactElement;\n /** Default props to merge with query params */\n defaultProps?: Partial<OgImageTemplateProps>;\n /** Google Fonts to load */\n fonts?: Array<{ family: string; weight: 400 | 500 | 600 | 700 | 800 | 900 }>;\n /** Image size */\n size?: { width: number; height: number };\n /** Enable debug mode */\n debug?: boolean;\n}\n\n/**\n * Factory function to create OG Image route handler\n * \n * @example\n * ```tsx\n * // app/api/og/route.tsx\n * import { createOgImageHandler } from '@djangocfg/nextjs/og-image';\n * import { MyTemplate } from '@/components/MyTemplate';\n * \n * export const { GET, runtime } = createOgImageHandler({\n * template: MyTemplate,\n * defaultProps: { siteName: 'My Site' },\n * fonts: [{ family: 'Inter', weight: 700 }],\n * });\n * ```\n */\nexport function createOgImageHandler(config: OgImageHandlerConfig) {\n const {\n template: Template = DefaultTemplate,\n defaultProps = {},\n fonts: fontConfig = [],\n size = { width: 1200, height: 630 },\n debug = false,\n } = config;\n\n async function GET(req: NextRequest) {\n let searchParams: URLSearchParams = new URLSearchParams();\n \n // Try to get searchParams from multiple sources\n if (req.nextUrl?.searchParams && req.nextUrl.searchParams.size > 0) {\n searchParams = req.nextUrl.searchParams;\n } else if (req.nextUrl?.search && req.nextUrl.search.length > 1) {\n searchParams = new URLSearchParams(req.nextUrl.search);\n } else {\n try {\n const url = new URL(req.url);\n if (url.searchParams.size > 0) {\n searchParams = url.searchParams;\n }\n } catch (error) {\n // Ignore\n }\n \n if (searchParams.size === 0 && req.url) {\n const queryIndex = req.url.indexOf('?');\n if (queryIndex !== -1) {\n const queryString = req.url.substring(queryIndex + 1);\n searchParams = new URLSearchParams(queryString);\n }\n }\n \n if (searchParams.size === 0) {\n const customParams = req.headers.get('x-og-search-params');\n if (customParams) {\n searchParams = new URLSearchParams(customParams);\n }\n }\n }\n\n // Initialize with defaults\n let title = defaultProps.title || 'Untitled';\n let subtitle = defaultProps.subtitle || '';\n let description = defaultProps.description || subtitle;\n\n // Support base64 data parameter (priority: base64 > query params > defaults)\n // All template props can be encoded in base64, including styling params\n const dataParam = searchParams.get('data');\n let decodedParams: Record<string, any> = {};\n \n if (dataParam) {\n try {\n const paramsObj: Record<string, string> = { data: dataParam };\n for (const [key, value] of searchParams.entries()) {\n if (key !== 'data') {\n paramsObj[key] = value;\n }\n }\n decodedParams = parseOgImageData(paramsObj);\n \n // Base64 data takes precedence - apply all decoded values\n if (decodedParams.title && typeof decodedParams.title === 'string' && decodedParams.title.trim() !== '') {\n title = decodedParams.title.trim();\n }\n if (decodedParams.subtitle && typeof decodedParams.subtitle === 'string' && decodedParams.subtitle.trim() !== '') {\n subtitle = decodedParams.subtitle.trim();\n }\n if (decodedParams.description && typeof decodedParams.description === 'string' && decodedParams.description.trim() !== '') {\n description = decodedParams.description.trim();\n }\n } catch (error) {\n // Silently fall back to defaults\n }\n }\n\n // Fallback to query params if not set from base64\n if (!title || title === 'Untitled') {\n const titleParam = searchParams.get('title');\n if (titleParam) {\n title = titleParam;\n }\n }\n if (!subtitle) {\n const subtitleParam = searchParams.get('subtitle');\n if (subtitleParam) {\n subtitle = subtitleParam;\n }\n }\n if (!description || description === subtitle) {\n const descParam = searchParams.get('description');\n if (descParam) {\n description = descParam;\n }\n }\n\n // Load fonts if configured\n let fonts: any[] = [];\n if (fontConfig.length > 0) {\n fonts = await loadGoogleFonts(fontConfig);\n }\n\n // Helper function to parse numeric/boolean values from decoded params\n const parseValue = (value: any, type: 'number' | 'boolean' | 'string' = 'string'): any => {\n if (value === undefined || value === null || value === '') {\n return undefined;\n }\n if (type === 'number') {\n const num = Number(value);\n return isNaN(num) ? undefined : num;\n }\n if (type === 'boolean') {\n if (typeof value === 'boolean') return value;\n if (typeof value === 'string') {\n return value.toLowerCase() === 'true' || value === '1';\n }\n return Boolean(value);\n }\n return String(value);\n };\n\n // Merge props: decoded params from URL override defaultProps\n const templateProps: OgImageTemplateProps = {\n ...defaultProps,\n // Content\n title,\n subtitle,\n description,\n // Override with decoded params if present\n siteName: decodedParams.siteName || defaultProps.siteName,\n logo: decodedParams.logo || defaultProps.logo,\n // Background\n backgroundType: (decodedParams.backgroundType as 'gradient' | 'solid') || defaultProps.backgroundType,\n gradientStart: decodedParams.gradientStart || defaultProps.gradientStart,\n gradientEnd: decodedParams.gradientEnd || defaultProps.gradientEnd,\n backgroundColor: decodedParams.backgroundColor || defaultProps.backgroundColor,\n // Typography - Title\n titleSize: parseValue(decodedParams.titleSize, 'number') ?? defaultProps.titleSize,\n titleWeight: parseValue(decodedParams.titleWeight, 'number') ?? defaultProps.titleWeight,\n titleColor: decodedParams.titleColor || defaultProps.titleColor,\n // Typography - Description\n descriptionSize: parseValue(decodedParams.descriptionSize, 'number') ?? defaultProps.descriptionSize,\n descriptionColor: decodedParams.descriptionColor || defaultProps.descriptionColor,\n // Typography - Site Name\n siteNameSize: parseValue(decodedParams.siteNameSize, 'number') ?? defaultProps.siteNameSize,\n siteNameColor: decodedParams.siteNameColor || defaultProps.siteNameColor,\n // Layout\n padding: parseValue(decodedParams.padding, 'number') ?? defaultProps.padding,\n logoSize: parseValue(decodedParams.logoSize, 'number') ?? defaultProps.logoSize,\n // Visibility flags\n showLogo: parseValue(decodedParams.showLogo, 'boolean') ?? defaultProps.showLogo,\n showSiteName: parseValue(decodedParams.showSiteName, 'boolean') ?? defaultProps.showSiteName,\n };\n\n\n return new ImageResponse(\n <Template {...templateProps} />,\n {\n width: size.width,\n height: size.height,\n fonts,\n debug: debug || process.env.NODE_ENV === 'development',\n }\n );\n }\n\n return {\n GET,\n runtime: 'edge' as const,\n };\n}\n\n/**\n * Create OG Image route handler for dynamic route with path parameter\n * \n * This is a convenience wrapper for Next.js dynamic routes like `/api/og/[data]/route.tsx`.\n * It extracts the `data` parameter from the path and passes it to the handler as a query parameter.\n * Also handles static export mode automatically.\n * \n * @example\n * ```tsx\n * // app/api/og/[data]/route.tsx\n * import { createOgImageDynamicRoute } from '@djangocfg/nextjs/og-image';\n * import { OgImageTemplate } from '@/components/OgImageTemplate';\n * \n * export const runtime = 'nodejs';\n * export const dynamic = 'force-static';\n * export const revalidate = false;\n * \n * const handler = createOgImageDynamicRoute({\n * template: OgImageTemplate,\n * defaultProps: {\n * siteName: 'My App',\n * logo: '/logo.svg',\n * },\n * });\n * \n * export async function GET(\n * request: NextRequest,\n * { params }: { params: { data: string } }\n ) {\n * return handler(request, params);\n * }\n * ```\n */\nexport function createOgImageDynamicRoute(config: OgImageHandlerConfig) {\n const handler = createOgImageHandler(config);\n const isStaticBuild = typeof process !== 'undefined' && process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';\n\n async function GET(\n request: NextRequest,\n context: { params: Promise<{ data: string }> }\n ) {\n // In static export mode, return a simple error response\n if (isStaticBuild) {\n return new Response('OG Image generation is not available in static export mode', {\n status: 404,\n headers: { 'Content-Type': 'text/plain' },\n });\n }\n\n // Await params (Next.js 15+ uses Promise)\n const params = await context.params;\n \n // Extract data from path parameter\n const dataParam = params.data;\n \n // Create a request with the data parameter as a query param for the handler\n const url = new URL(request.url);\n url.searchParams.set('data', dataParam);\n \n const modifiedRequest = new NextRequest(url.toString(), {\n method: request.method,\n headers: request.headers,\n });\n\n return handler.GET(modifiedRequest);\n }\n\n // For static export, provide generateStaticParams that returns empty array\n // This allows the route to be excluded from static build\n async function generateStaticParams(): Promise<Array<{ data: string }>> {\n return [];\n }\n\n return {\n GET,\n generateStaticParams,\n };\n}\n","/**\n * Default OG Image Template\n *\n * A modern, gradient-based template for OG images\n */\n\nimport type { ReactElement } from 'react';\nimport type { OgImageTemplateProps } from '../types';\n\n/**\n * Default OG Image Template Component\n *\n * Features:\n * - Modern gradient background\n * - Responsive text sizing\n * - Optional logo and site name\n * - Clean typography\n * - Full customization support\n *\n * @param props - Template props with title, description, siteName, logo and optional customization\n */\nexport function DefaultTemplate({\n title,\n description,\n siteName,\n logo,\n // Visibility flags\n showLogo = true,\n showSiteName = true,\n // Background customization\n backgroundType = 'gradient',\n gradientStart = '#667eea',\n gradientEnd = '#764ba2',\n backgroundColor = '#ffffff',\n // Typography - Title\n titleSize,\n titleWeight = 800,\n titleColor = 'white',\n // Typography - Description\n descriptionSize = 32,\n descriptionColor = 'rgba(255, 255, 255, 0.85)',\n // Typography - Site Name\n siteNameSize = 28,\n siteNameColor = 'rgba(255, 255, 255, 0.95)',\n // Layout\n padding = 80,\n logoSize = 48,\n // Dev mode\n devMode = false,\n}: OgImageTemplateProps): ReactElement {\n // Calculate title size if not provided (responsive based on title length)\n const calculatedTitleSize = titleSize || (title.length > 60 ? 56 : 72);\n \n // Determine background style\n const backgroundStyle =\n backgroundType === 'gradient'\n ? `linear-gradient(135deg, ${gradientStart} 0%, ${gradientEnd} 100%)`\n : backgroundColor;\n\n // Grid overlay for dev mode\n const gridOverlay = devMode ? (\n <div\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundImage: `\n linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px),\n linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px)\n `,\n backgroundSize: '20px 20px',\n pointerEvents: 'none',\n zIndex: 10,\n }}\n />\n ) : null;\n\n return (\n <div\n style={{\n height: '100%',\n width: '100%',\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-start',\n justifyContent: 'space-between',\n background: backgroundStyle,\n padding: `${padding}px`,\n fontFamily: 'system-ui, -apple-system, sans-serif',\n position: 'relative',\n }}\n >\n {gridOverlay}\n \n {/* Header with logo and site name */}\n {((showLogo && logo) || (showSiteName && siteName)) && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: '16px',\n }}\n >\n {showLogo && logo && (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={logo}\n alt=\"Logo\"\n width={logoSize}\n height={logoSize}\n style={{\n borderRadius: '8px',\n }}\n />\n )}\n {showSiteName && siteName && (\n <div\n style={{\n fontSize: siteNameSize,\n fontWeight: 600,\n color: siteNameColor,\n letterSpacing: '-0.02em',\n }}\n >\n {siteName}\n </div>\n )}\n </div>\n )}\n\n {/* Main content */}\n <div\n style={{\n display: 'flex',\n flexDirection: 'column',\n gap: '24px',\n flex: 1,\n justifyContent: 'center',\n }}\n >\n {/* Title */}\n <div\n style={{\n fontSize: calculatedTitleSize,\n fontWeight: titleWeight,\n color: titleColor,\n lineHeight: 1.1,\n letterSpacing: '-0.03em',\n textShadow: backgroundType === 'gradient' ? '0 2px 20px rgba(0, 0, 0, 0.2)' : 'none',\n maxWidth: '100%',\n wordWrap: 'break-word',\n }}\n >\n {title}\n </div>\n\n {/* Description */}\n {description && (\n <div\n style={{\n fontSize: descriptionSize,\n fontWeight: 400,\n color: descriptionColor,\n lineHeight: 1.5,\n letterSpacing: '-0.01em',\n maxWidth: '90%',\n display: '-webkit-box',\n WebkitLineClamp: 2,\n WebkitBoxOrient: 'vertical',\n overflow: 'hidden',\n }}\n >\n {description}\n </div>\n )}\n </div>\n\n {/* Footer decoration */}\n <div\n style={{\n display: 'flex',\n width: '100%',\n height: '4px',\n background: backgroundType === 'gradient' \n ? `linear-gradient(90deg, ${gradientStart} 0%, ${gradientEnd} 100%)`\n : gradientStart,\n borderRadius: '2px',\n }}\n />\n </div>\n );\n}\n\n/**\n * Simple light template variant\n * \n * Light background variant with dark text\n */\nexport function LightTemplate({\n title,\n description,\n siteName,\n logo,\n // Visibility flags\n showLogo = true,\n showSiteName = true,\n // Background customization (defaults to light theme)\n backgroundType = 'solid',\n gradientStart = '#667eea',\n gradientEnd = '#764ba2',\n backgroundColor = '#ffffff',\n // Typography - Title\n titleSize,\n titleWeight = 800,\n titleColor = '#111',\n // Typography - Description\n descriptionSize = 32,\n descriptionColor = '#666',\n // Typography - Site Name\n siteNameSize = 28,\n siteNameColor = '#111',\n // Layout\n padding = 80,\n logoSize = 48,\n // Dev mode\n devMode = false,\n}: OgImageTemplateProps): ReactElement {\n // Calculate title size if not provided (responsive based on title length)\n const calculatedTitleSize = titleSize || (title.length > 60 ? 56 : 72);\n \n // Determine background style\n const backgroundStyle =\n backgroundType === 'gradient'\n ? `linear-gradient(135deg, ${gradientStart} 0%, ${gradientEnd} 100%)`\n : backgroundColor;\n\n // Grid overlay for dev mode\n const gridOverlay = devMode ? (\n <div\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundImage: `\n linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px),\n linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px)\n `,\n backgroundSize: '20px 20px',\n pointerEvents: 'none',\n zIndex: 10,\n }}\n />\n ) : null;\n\n return (\n <div\n style={{\n height: '100%',\n width: '100%',\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-start',\n justifyContent: 'space-between',\n background: backgroundStyle,\n padding: `${padding}px`,\n fontFamily: 'system-ui, -apple-system, sans-serif',\n position: 'relative',\n }}\n >\n {gridOverlay}\n \n {/* Header with logo and site name */}\n {((showLogo && logo) || (showSiteName && siteName)) && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: '16px',\n }}\n >\n {showLogo && logo && (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={logo}\n alt=\"Logo\"\n width={logoSize}\n height={logoSize}\n style={{\n borderRadius: '8px',\n }}\n />\n )}\n {showSiteName && siteName && (\n <div\n style={{\n fontSize: siteNameSize,\n fontWeight: 600,\n color: siteNameColor,\n letterSpacing: '-0.02em',\n }}\n >\n {siteName}\n </div>\n )}\n </div>\n )}\n\n {/* Main content */}\n <div\n style={{\n display: 'flex',\n flexDirection: 'column',\n gap: '24px',\n flex: 1,\n justifyContent: 'center',\n }}\n >\n {/* Title */}\n <div\n style={{\n fontSize: calculatedTitleSize,\n fontWeight: titleWeight,\n color: titleColor,\n lineHeight: 1.1,\n letterSpacing: '-0.03em',\n maxWidth: '100%',\n wordWrap: 'break-word',\n }}\n >\n {title}\n </div>\n\n {/* Description */}\n {description && (\n <div\n style={{\n fontSize: descriptionSize,\n fontWeight: 400,\n color: descriptionColor,\n lineHeight: 1.5,\n letterSpacing: '-0.01em',\n maxWidth: '90%',\n }}\n >\n {description}\n </div>\n )}\n </div>\n\n {/* Footer decoration */}\n <div\n style={{\n display: 'flex',\n width: '100%',\n height: '4px',\n background: backgroundType === 'gradient' \n ? `linear-gradient(90deg, ${gradientStart} 0%, ${gradientEnd} 100%)`\n : gradientStart,\n borderRadius: '2px',\n }}\n />\n </div>\n );\n}\n\n","/**\n * Font Utilities for OG Image Generation\n *\n * Provides dynamic font loading from Google Fonts without requiring files in public/\n * Based on Vercel's official @vercel/og documentation\n */\n\nexport interface FontConfig {\n name: string;\n weight?: 400 | 500 | 600 | 700 | 800 | 900;\n style?: 'normal' | 'italic';\n data: ArrayBuffer;\n}\n\n/**\n * Load a Google Font dynamically\n *\n * @param font - Font family name (e.g., \"Inter\", \"Roboto\", \"Manrope\")\n * @param text - Text to optimize font for (optional, reduces file size)\n * @param weight - Font weight (default: 700)\n * @returns ArrayBuffer of font data\n *\n * @example\n * const fontData = await loadGoogleFont('Manrope', 'Hello World', 700);\n */\nexport async function loadGoogleFont(\n font: string,\n text?: string,\n weight: number = 700\n): Promise<ArrayBuffer> {\n // Construct Google Fonts API URL\n let url = `https://fonts.googleapis.com/css2?family=${font}:wght@${weight}`;\n\n // Add text parameter to optimize font subset (reduces size)\n if (text) {\n url += `&text=${encodeURIComponent(text)}`;\n }\n\n try {\n // Fetch CSS containing font URL\n const css = await fetch(url, {\n headers: {\n // Required to get TTF format instead of WOFF2\n 'User-Agent':\n 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1',\n },\n }).then((res) => res.text());\n\n // Extract font URL from CSS\n const resource = css.match(/src: url\\((.+)\\) format\\('(opentype|truetype)'\\)/);\n\n if (!resource || !resource[1]) {\n throw new Error(`Failed to parse font URL from CSS for font: ${font}`);\n }\n\n // Fetch actual font file\n const response = await fetch(resource[1]);\n\n if (response.status !== 200) {\n throw new Error(`Failed to fetch font data: HTTP ${response.status}`);\n }\n\n return await response.arrayBuffer();\n } catch (error) {\n console.error(`Error loading Google Font \"${font}\":`, error);\n throw new Error(`Failed to load font \"${font}\": ${error instanceof Error ? error.message : 'Unknown error'}`);\n }\n}\n\n/**\n * Load multiple Google Fonts\n *\n * @param fonts - Array of font configurations to load\n * @returns Array of FontConfig objects ready for ImageResponse\n *\n * @example\n * const fonts = await loadGoogleFonts([\n * { family: 'Manrope', weight: 700 },\n * { family: 'Inter', weight: 400 }\n * ]);\n */\nexport async function loadGoogleFonts(\n fonts: Array<{\n family: string;\n weight?: 400 | 500 | 600 | 700 | 800 | 900;\n style?: 'normal' | 'italic';\n text?: string;\n }>\n): Promise<FontConfig[]> {\n const fontConfigs = await Promise.all(\n fonts.map(async ({ family, weight = 700, style = 'normal', text }) => {\n const data = await loadGoogleFont(family, text, weight);\n return {\n name: family,\n weight,\n style,\n data,\n };\n })\n );\n\n return fontConfigs;\n}\n\n/**\n * Create a font loader with caching\n *\n * Useful for reusing font data across multiple OG image requests\n *\n * @example\n * const fontLoader = createFontLoader();\n * const font = await fontLoader.load('Manrope', 700);\n */\nexport function createFontLoader() {\n const cache = new Map<string, Promise<ArrayBuffer>>();\n\n return {\n /**\n * Load a font with caching\n */\n async load(\n family: string,\n weight: number = 700,\n text?: string\n ): Promise<ArrayBuffer> {\n const cacheKey = `${family}-${weight}-${text || 'all'}`;\n\n if (!cache.has(cacheKey)) {\n cache.set(cacheKey, loadGoogleFont(family, text, weight));\n }\n\n return cache.get(cacheKey)!;\n },\n\n /**\n * Clear the cache\n */\n clear() {\n cache.clear();\n },\n\n /**\n * Get cache size\n */\n size() {\n return cache.size;\n },\n };\n}\n\n","/**\n * URL Generation Helpers for OG Images\n *\n * Utilities to generate OG image URLs with proper query parameters\n */\n\n/** Default OG Image API base URL */\nconst DEFAULT_OG_IMAGE_BASE_URL = 'https://djangocfg.com/api/og';\n\n/**\n * Encode string to base64 with Unicode support\n * Works in both browser and Node.js environments\n */\nfunction encodeBase64(str: string): string {\n // Node.js environment\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(str, 'utf-8').toString('base64');\n }\n // Browser environment - handle Unicode via UTF-8 encoding\n return btoa(unescape(encodeURIComponent(str)));\n}\n\n/**\n * Decode base64 string with Unicode support\n * Works in both browser, Node.js, and Edge Runtime environments\n */\nfunction decodeBase64(str: string): string {\n // Node.js environment\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(str, 'base64').toString('utf-8');\n }\n // Edge Runtime / Browser environment - handle Unicode via UTF-8 decoding\n // atob is available in Edge Runtime\n try {\n const binaryString = atob(str);\n // Convert binary string to UTF-8\n return decodeURIComponent(\n binaryString\n .split('')\n .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join('')\n );\n } catch (error) {\n // Fallback to simpler method if above fails\n return decodeURIComponent(escape(atob(str)));\n }\n}\n\n/**\n * OG Image URL parameters\n * All parameters can be encoded in URL via base64\n */\nexport interface OgImageUrlParams {\n /** Page title */\n title: string;\n /** Page description (optional) */\n description?: string;\n /** Site name (optional) */\n siteName?: string;\n /** Logo URL (optional) */\n logo?: string;\n /** Background type: 'gradient' or 'solid' */\n backgroundType?: 'gradient' | 'solid';\n /** Gradient start color (hex) */\n gradientStart?: string;\n /** Gradient end color (hex) */\n gradientEnd?: string;\n /** Background color (for solid type) */\n backgroundColor?: string;\n /** Title font size (px) */\n titleSize?: number;\n /** Title font weight */\n titleWeight?: number;\n /** Title text color */\n titleColor?: string;\n /** Description font size (px) */\n descriptionSize?: number;\n /** Description text color */\n descriptionColor?: string;\n /** Site name font size (px) */\n siteNameSize?: number;\n /** Site name text color */\n siteNameColor?: string;\n /** Padding (px) */\n padding?: number;\n /** Logo size (px) */\n logoSize?: number;\n /** Show logo flag */\n showLogo?: boolean;\n /** Show site name flag */\n showSiteName?: boolean;\n /** Additional custom parameters */\n [key: string]: string | number | boolean | undefined;\n}\n\n/**\n * Options for generating OG image URL\n */\nexport interface GenerateOgImageUrlOptions {\n /**\n * Base URL of the OG image API route\n * @default 'https://djangocfg.com/api/og'\n */\n baseUrl?: string;\n /**\n * If true, encode params as base64 for safer URLs\n * @default true\n */\n useBase64?: boolean;\n}\n\n/**\n * Generate OG image URL with query parameters or base64 encoding\n *\n * @param params - URL parameters for the OG image\n * @param options - Generation options (baseUrl, useBase64)\n * @returns Complete OG image URL with encoded parameters\n *\n * @example\n * ```typescript\n * // Using default baseUrl (https://djangocfg.com/api/og)\n * const url = generateOgImageUrl({\n * title: 'My Page Title',\n * description: 'Page description here',\n * });\n *\n * // With custom baseUrl\n * const url = generateOgImageUrl(\n * { title: 'My Page' },\n * { baseUrl: '/api/og' }\n * );\n * ```\n */\nexport function generateOgImageUrl(\n params: OgImageUrlParams,\n options: GenerateOgImageUrlOptions = {}\n): string {\n const {\n baseUrl = DEFAULT_OG_IMAGE_BASE_URL,\n useBase64 = true\n } = options;\n\n if (useBase64) {\n // Clean params - remove undefined/null/empty values\n const cleanParams: Record<string, string | number | boolean> = {};\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null && value !== '') {\n cleanParams[key] = value;\n }\n });\n\n // Encode as base64 (Unicode-safe)\n const jsonString = JSON.stringify(cleanParams);\n const base64Data = encodeBase64(jsonString);\n\n // CRITICAL: Use path parameter instead of query parameter\n // Next.js strips query params in internal requests for metadata generation\n // Using /api/og/[data] instead of /api/og?data=... preserves the data\n return `${baseUrl}/${base64Data}`;\n } else {\n // Legacy query params mode\n const searchParams = new URLSearchParams();\n\n // Add all defined parameters\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null && value !== '') {\n searchParams.append(key, String(value));\n }\n });\n\n const query = searchParams.toString();\n return query ? `${baseUrl}?${query}` : baseUrl;\n }\n}\n\n/**\n * Get absolute OG image URL from relative path\n *\n * Useful for generating absolute URLs required by Open Graph meta tags\n *\n * @param relativePath - Relative OG image path (e.g., '/api/og?title=Hello')\n * @param siteUrl - Base site URL (e.g., 'https://example.com')\n * @returns Absolute URL\n *\n * @example\n * ```typescript\n * const absolute = getAbsoluteOgImageUrl(\n * '/api/og?title=Hello',\n * 'https://example.com'\n * );\n * // Result: https://example.com/api/og?title=Hello\n * ```\n */\nexport function getAbsoluteOgImageUrl(\n relativePath: string,\n siteUrl: string\n): string {\n // If path is already an absolute URL, return as-is\n if (relativePath.startsWith('http://') || relativePath.startsWith('https://')) {\n return relativePath;\n }\n\n // Remove trailing slash from site URL\n const cleanSiteUrl = siteUrl.replace(/\\/$/, '');\n\n // Ensure relative path starts with /\n const cleanPath = relativePath.startsWith('/')\n ? relativePath\n : `/${relativePath}`;\n\n return `${cleanSiteUrl}${cleanPath}`;\n}\n\n/**\n * Create OG image URL builder with preset configuration\n *\n * Useful when you want to reuse the same base URL and default parameters\n *\n * @param defaults - Default parameters to merge with each URL generation\n * @param options - Default options (baseUrl, useBase64)\n * @returns URL builder function\n *\n * @example\n * ```typescript\n * const buildOgUrl = createOgImageUrlBuilder(\n * { siteName: 'My Site', logo: '/logo.png' },\n * { baseUrl: '/api/og' }\n * );\n *\n * const url1 = buildOgUrl({ title: 'Page 1' });\n * const url2 = buildOgUrl({ title: 'Page 2', description: 'Custom desc' });\n * ```\n */\nexport function createOgImageUrlBuilder(\n defaults: Partial<OgImageUrlParams> = {},\n options: GenerateOgImageUrlOptions = {}\n) {\n return (params: OgImageUrlParams): string => {\n return generateOgImageUrl(\n { ...defaults, ...params },\n options\n );\n };\n}\n\n/**\n * Parse OG image URL parameters from a URL string (legacy query params)\n *\n * @param url - Full or relative URL with query parameters\n * @returns Parsed parameters object\n *\n * @example\n * ```typescript\n * const params = parseOgImageUrl('/api/og?title=Hello&description=World');\n * // Result: { title: 'Hello', description: 'World' }\n * ```\n */\nexport function parseOgImageUrl(url: string): Record<string, string> {\n try {\n const urlObj = new URL(url, 'http://dummy.com');\n const params: Record<string, string> = {};\n\n urlObj.searchParams.forEach((value, key) => {\n params[key] = value;\n });\n\n return params;\n } catch {\n return {};\n }\n}\n\n/**\n * Parse OG image data from base64-encoded query parameter\n *\n * Use this in your API route to decode the `data` parameter\n * Supports both base64 (new) and legacy query params format\n *\n * @param searchParams - URL search params or request object\n * @returns Parsed OG image parameters\n *\n * @example\n * ```typescript\n * // In Next.js API route (pages/api/og.ts)\n * export default function handler(req) {\n * const params = parseOgImageData(req.query);\n * // { title: 'Hello', description: 'World' }\n * }\n *\n * // In Next.js App Router (app/api/og/route.ts)\n * export async function GET(request: Request) {\n * const { searchParams } = new URL(request.url);\n * const params = parseOgImageData(Object.fromEntries(searchParams));\n * // { title: 'Hello', description: 'World' }\n * }\n * ```\n */\nexport function parseOgImageData(\n searchParams: Record<string, string | string[] | undefined> | URLSearchParams\n): Record<string, string> {\n try {\n // Handle URLSearchParams\n let params: Record<string, string | undefined>;\n\n if (searchParams instanceof URLSearchParams) {\n // Convert URLSearchParams to object\n params = {};\n for (const [key, value] of searchParams.entries()) {\n params[key] = value;\n }\n } else {\n params = searchParams as Record<string, string | undefined>;\n }\n\n // Debug logging\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Input params keys:', Object.keys(params));\n console.log('[parseOgImageData] Input params:', params);\n }\n\n // Check for base64-encoded data parameter\n const dataParam = params.data;\n if (dataParam && typeof dataParam === 'string' && dataParam.trim() !== '') {\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Found data param, length:', dataParam.length);\n }\n\n try {\n const decoded = decodeBase64(dataParam);\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Decoded string:', decoded.substring(0, 100));\n }\n\n const parsed = JSON.parse(decoded);\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Parsed JSON:', parsed);\n }\n\n // Ensure all values are strings\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(parsed)) {\n if (value !== undefined && value !== null) {\n result[key] = String(value);\n }\n }\n\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Result:', result);\n }\n\n return result;\n } catch (decodeError) {\n console.error('[parseOgImageData] Error decoding/parsing data param:', decodeError);\n if (decodeError instanceof Error) {\n console.error('[parseOgImageData] Error message:', decodeError.message);\n }\n // Fall through to legacy query params\n }\n } else {\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] No data param found or empty');\n }\n }\n\n // Fallback to legacy query params format\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(params)) {\n if (key !== 'data' && value !== undefined && value !== null) {\n result[key] = Array.isArray(value) ? value[0] : String(value);\n }\n }\n\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Fallback result:', result);\n }\n\n return result;\n } catch (error) {\n console.error('[parseOgImageData] Unexpected error:', error);\n return {};\n }\n}\n\n// Export base64 utilities for advanced use cases\nexport { encodeBase64, decodeBase64 };\n","/**\n * Metadata Utilities for OG Images\n *\n * Helpers to automatically add og:image to Next.js metadata\n */\n\nimport type { Metadata } from 'next';\nimport { generateOgImageUrl, getAbsoluteOgImageUrl, OgImageUrlParams} from './url';\n\n/**\n * Options for generating OG image metadata\n */\nexport interface OgImageMetadataOptions {\n /** Base URL of the OG image API route (e.g., '/api/og') */\n ogImageBaseUrl?: string;\n /** Site URL for absolute URLs (e.g., 'https://example.com') */\n siteUrl?: string;\n /** Default parameters to merge with page-specific params */\n defaultParams?: Partial<OgImageUrlParams>;\n /** Whether to use base64 encoding (default: true) */\n useBase64?: boolean;\n}\n\n/**\n * Extract title from metadata\n */\nfunction extractTitle(metadata: Metadata): string {\n if (typeof metadata.title === 'string') {\n return metadata.title;\n }\n if (metadata.title) {\n if ('default' in metadata.title) {\n return metadata.title.default;\n }\n if ('absolute' in metadata.title) {\n return metadata.title.absolute;\n }\n }\n return '';\n}\n\n/**\n * Extract description from metadata\n */\nfunction extractDescription(metadata: Metadata): string {\n if (typeof metadata.description === 'string') {\n return metadata.description;\n }\n return '';\n}\n\n/**\n * Generate Next.js metadata with OG image\n *\n * Automatically adds og:image, twitter:image, and other OG meta tags\n * Automatically extracts title and description from metadata if not provided\n *\n * @param metadata - Base metadata object\n * @param ogImageParams - Optional parameters for OG image generation (if not provided, extracted from metadata)\n * @param options - Configuration options\n * @returns Enhanced metadata with OG image\n *\n * @example\n * ```typescript\n * // In page.tsx or layout.tsx\n * import { generateOgImageMetadata } from '@djangocfg/nextjs/og-image';\n * import { settings } from '@/core/settings';\n *\n * export const metadata = generateOgImageMetadata(\n * {\n * title: 'My Page',\n * description: 'Page description',\n * },\n * undefined, // Will auto-extract from metadata\n * {\n * ogImageBaseUrl: '/api/og',\n * siteUrl: settings.app.siteUrl,\n * defaultParams: {\n * siteName: settings.app.name,\n * logo: settings.app.icons.logoVector,\n * },\n * }\n * );\n * ```\n */\n/**\n * Get site URL automatically from environment\n * Priority: NEXT_PUBLIC_SITE_URL > VERCEL_URL > fallback\n */\nfunction getSiteUrl(): string {\n // Try NEXT_PUBLIC_SITE_URL first (most reliable)\n if (typeof process !== 'undefined' && process.env.NEXT_PUBLIC_SITE_URL) {\n return process.env.NEXT_PUBLIC_SITE_URL;\n }\n \n // Development fallback\n return '';\n}\n\nexport function generateOgImageMetadata(\n metadata: Metadata,\n ogImageParams?: Partial<OgImageUrlParams>,\n options: OgImageMetadataOptions = {}\n): Metadata {\n const {\n ogImageBaseUrl = 'https://djangocfg.com/api/og',\n siteUrl: providedSiteUrl,\n defaultParams = {},\n useBase64 = true,\n } = options;\n\n // Automatically determine siteUrl if not provided or is undefined\n const siteUrl = providedSiteUrl && providedSiteUrl !== 'undefined' \n ? providedSiteUrl \n : getSiteUrl();\n\n // Auto-extract title and description from metadata if not provided\n const extractedTitle = extractTitle(metadata);\n const extractedDescription = extractDescription(metadata);\n\n // Merge with provided params (provided params take precedence)\n const finalOgImageParams: OgImageUrlParams = {\n ...defaultParams,\n title: ogImageParams?.title || extractedTitle || defaultParams.title || '',\n description: ogImageParams?.description || extractedDescription || defaultParams.description || '',\n ...ogImageParams,\n };\n\n // Get alt text for image (title or siteName as fallback)\n const imageAlt = finalOgImageParams.title || finalOgImageParams.siteName;\n\n // Generate relative OG image URL\n const relativeOgImageUrl = generateOgImageUrl(\n finalOgImageParams,\n { baseUrl: ogImageBaseUrl, useBase64 }\n );\n\n // CRITICAL: Use absolute URL to ensure query params are preserved\n // Next.js might strip query params from relative URLs in some cases\n // Absolute URLs ensure the full URL with params is used\n const ogImageUrl = siteUrl\n ? getAbsoluteOgImageUrl(relativeOgImageUrl, siteUrl)\n : relativeOgImageUrl;\n\n // Normalize existing images to arrays\n const existingOgImages = metadata.openGraph?.images\n ? Array.isArray(metadata.openGraph.images)\n ? metadata.openGraph.images\n : [metadata.openGraph.images]\n : [];\n\n const existingTwitterImages = metadata.twitter?.images\n ? Array.isArray(metadata.twitter.images)\n ? metadata.twitter.images\n : [metadata.twitter.images]\n : [];\n\n // Build final metadata object\n const finalMetadata: Metadata = {\n ...metadata,\n openGraph: {\n ...metadata.openGraph,\n images: [\n ...existingOgImages,\n {\n url: ogImageUrl,\n width: 1200,\n height: 630,\n alt: imageAlt,\n },\n ],\n },\n twitter: {\n ...metadata.twitter,\n card: 'summary_large_image',\n images: [\n ...existingTwitterImages,\n {\n url: ogImageUrl,\n alt: imageAlt,\n },\n ],\n },\n };\n\n // Automatically add metadataBase if siteUrl is an absolute URL\n // metadataBase requires absolute URL - skip if siteUrl is relative path (like /cfg/admin)\n // Only add if not already set in input metadata\n if (!finalMetadata.metadataBase && siteUrl) {\n // Check if siteUrl is an absolute URL (starts with http:// or https://)\n if (siteUrl.startsWith('http://') || siteUrl.startsWith('https://')) {\n try {\n finalMetadata.metadataBase = new URL(siteUrl);\n } catch (e) {\n // If URL construction fails, skip metadataBase\n // This shouldn't happen if we check for http/https, but just in case\n }\n }\n }\n\n return finalMetadata;\n}\n\n/**\n * Create OG image metadata generator with preset configuration\n *\n * Useful when you want to reuse the same configuration across multiple pages\n *\n * @param options - Configuration options\n * @returns Metadata generator function\n *\n * @example\n * ```typescript\n * // In a shared file (e.g., lib/metadata.ts)\n * import { createOgImageMetadataGenerator } from '@djangocfg/nextjs/og-image';\n * import { settings } from '@/core/settings';\n *\n * export const generateMetadata = createOgImageMetadataGenerator({\n * ogImageBaseUrl: '/api/og',\n * siteUrl: settings.app.siteUrl,\n * defaultParams: {\n * siteName: settings.app.name,\n * logo: settings.app.icons.logoVector,\n * },\n * });\n *\n * // In page.tsx\n * import { generateMetadata } from '@/lib/metadata';\n *\n * export const metadata = generateMetadata({\n * title: 'My Page',\n * description: 'Description',\n * });\n * ```\n */\nexport function createOgImageMetadataGenerator(\n options: OgImageMetadataOptions\n) {\n return (\n metadata: Metadata,\n ogImageParams?: Partial<OgImageUrlParams>\n ): Metadata => {\n return generateOgImageMetadata(metadata, ogImageParams, options);\n };\n}\n\n"],"mappings":"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","names":["result","jsx"]}
1
+ {"version":3,"sources":["../../src/og-image/route.tsx","../../src/og-image/components/DefaultTemplate.tsx","../../src/og-image/utils/fonts.ts","../../src/og-image/utils/url.ts","../../src/og-image/utils/metadata.ts"],"sourcesContent":["/**\n * OG Image Route Handler\n *\n * Factory function to create OG Image route handler for Next.js App Router\n *\n * Usage:\n * ```tsx\n * // app/api/og/route.tsx\n * import { createOgImageHandler } from '@djangocfg/nextjs/og-image';\n * import { MyTemplate } from './templates';\n *\n * export const { GET, runtime } = createOgImageHandler({\n * template: MyTemplate,\n * defaultProps: {\n * siteName: 'My Site',\n * },\n * fonts: [{ family: 'Manrope', weight: 700 }],\n * });\n * ```\n */\n\nimport { ImageResponse } from 'next/og';\nimport { NextRequest } from 'next/server';\n\nimport { DefaultTemplate } from './components/DefaultTemplate';\nimport { loadGoogleFonts } from './utils';\nimport { parseOgImageData } from './utils/url';\n\nimport type { ReactElement } from 'react';\nimport type { OgImageTemplateProps } from './types';\n\nexport interface OgImageHandlerConfig {\n /** Custom template component (optional, defaults to DefaultTemplate) */\n template?: (props: OgImageTemplateProps) => ReactElement;\n /** Default props to merge with query params */\n defaultProps?: Partial<OgImageTemplateProps>;\n /** Google Fonts to load */\n fonts?: Array<{ family: string; weight: 400 | 500 | 600 | 700 | 800 | 900 }>;\n /** Image size */\n size?: { width: number; height: number };\n /** Enable debug mode */\n debug?: boolean;\n}\n\n/**\n * Factory function to create OG Image route handler\n * \n * @example\n * ```tsx\n * // app/api/og/route.tsx\n * import { createOgImageHandler } from '@djangocfg/nextjs/og-image';\n * import { MyTemplate } from '@/components/MyTemplate';\n * \n * export const { GET, runtime } = createOgImageHandler({\n * template: MyTemplate,\n * defaultProps: { siteName: 'My Site' },\n * fonts: [{ family: 'Inter', weight: 700 }],\n * });\n * ```\n */\nexport function createOgImageHandler(config: OgImageHandlerConfig) {\n const {\n template: Template = DefaultTemplate,\n defaultProps = {},\n fonts: fontConfig = [],\n size = { width: 1200, height: 630 },\n debug = false,\n } = config;\n\n async function GET(req: NextRequest) {\n let searchParams: URLSearchParams = new URLSearchParams();\n \n // Try to get searchParams from multiple sources\n if (req.nextUrl?.searchParams && req.nextUrl.searchParams.size > 0) {\n searchParams = req.nextUrl.searchParams;\n } else if (req.nextUrl?.search && req.nextUrl.search.length > 1) {\n searchParams = new URLSearchParams(req.nextUrl.search);\n } else {\n try {\n const url = new URL(req.url);\n if (url.searchParams.size > 0) {\n searchParams = url.searchParams;\n }\n } catch (error) {\n // Ignore\n }\n \n if (searchParams.size === 0 && req.url) {\n const queryIndex = req.url.indexOf('?');\n if (queryIndex !== -1) {\n const queryString = req.url.substring(queryIndex + 1);\n searchParams = new URLSearchParams(queryString);\n }\n }\n \n if (searchParams.size === 0) {\n const customParams = req.headers.get('x-og-search-params');\n if (customParams) {\n searchParams = new URLSearchParams(customParams);\n }\n }\n }\n\n // Initialize with defaults\n let title = defaultProps.title || 'Untitled';\n let subtitle = defaultProps.subtitle || '';\n let description = defaultProps.description || subtitle;\n\n // Support base64 data parameter (priority: base64 > query params > defaults)\n // All template props can be encoded in base64, including styling params\n const dataParam = searchParams.get('data');\n let decodedParams: Record<string, any> = {};\n \n if (dataParam) {\n try {\n const paramsObj: Record<string, string> = { data: dataParam };\n for (const [key, value] of searchParams.entries()) {\n if (key !== 'data') {\n paramsObj[key] = value;\n }\n }\n decodedParams = parseOgImageData(paramsObj);\n \n // Base64 data takes precedence - apply all decoded values\n if (decodedParams.title && typeof decodedParams.title === 'string' && decodedParams.title.trim() !== '') {\n title = decodedParams.title.trim();\n }\n if (decodedParams.subtitle && typeof decodedParams.subtitle === 'string' && decodedParams.subtitle.trim() !== '') {\n subtitle = decodedParams.subtitle.trim();\n }\n if (decodedParams.description && typeof decodedParams.description === 'string' && decodedParams.description.trim() !== '') {\n description = decodedParams.description.trim();\n }\n } catch (error) {\n // Silently fall back to defaults\n }\n }\n\n // Fallback to query params if not set from base64\n if (!title || title === 'Untitled') {\n const titleParam = searchParams.get('title');\n if (titleParam) {\n title = titleParam;\n }\n }\n if (!subtitle) {\n const subtitleParam = searchParams.get('subtitle');\n if (subtitleParam) {\n subtitle = subtitleParam;\n }\n }\n if (!description || description === subtitle) {\n const descParam = searchParams.get('description');\n if (descParam) {\n description = descParam;\n }\n }\n\n // Load fonts if configured\n let fonts: any[] = [];\n if (fontConfig.length > 0) {\n fonts = await loadGoogleFonts(fontConfig);\n }\n\n // Helper function to parse numeric/boolean values from decoded params\n const parseValue = (value: any, type: 'number' | 'boolean' | 'string' = 'string'): any => {\n if (value === undefined || value === null || value === '') {\n return undefined;\n }\n if (type === 'number') {\n const num = Number(value);\n return isNaN(num) ? undefined : num;\n }\n if (type === 'boolean') {\n if (typeof value === 'boolean') return value;\n if (typeof value === 'string') {\n return value.toLowerCase() === 'true' || value === '1';\n }\n return Boolean(value);\n }\n return String(value);\n };\n\n // Merge props: decoded params from URL override defaultProps\n const templateProps: OgImageTemplateProps = {\n ...defaultProps,\n // Content\n title,\n subtitle,\n description,\n // Override with decoded params if present\n siteName: decodedParams.siteName || defaultProps.siteName,\n logo: decodedParams.logo || defaultProps.logo,\n // Background\n backgroundType: (decodedParams.backgroundType as 'gradient' | 'solid') || defaultProps.backgroundType,\n gradientStart: decodedParams.gradientStart || defaultProps.gradientStart,\n gradientEnd: decodedParams.gradientEnd || defaultProps.gradientEnd,\n backgroundColor: decodedParams.backgroundColor || defaultProps.backgroundColor,\n // Typography - Title\n titleSize: parseValue(decodedParams.titleSize, 'number') ?? defaultProps.titleSize,\n titleWeight: parseValue(decodedParams.titleWeight, 'number') ?? defaultProps.titleWeight,\n titleColor: decodedParams.titleColor || defaultProps.titleColor,\n // Typography - Description\n descriptionSize: parseValue(decodedParams.descriptionSize, 'number') ?? defaultProps.descriptionSize,\n descriptionColor: decodedParams.descriptionColor || defaultProps.descriptionColor,\n // Typography - Site Name\n siteNameSize: parseValue(decodedParams.siteNameSize, 'number') ?? defaultProps.siteNameSize,\n siteNameColor: decodedParams.siteNameColor || defaultProps.siteNameColor,\n // Layout\n padding: parseValue(decodedParams.padding, 'number') ?? defaultProps.padding,\n logoSize: parseValue(decodedParams.logoSize, 'number') ?? defaultProps.logoSize,\n // Visibility flags\n showLogo: parseValue(decodedParams.showLogo, 'boolean') ?? defaultProps.showLogo,\n showSiteName: parseValue(decodedParams.showSiteName, 'boolean') ?? defaultProps.showSiteName,\n };\n\n\n return new ImageResponse(\n <Template {...templateProps} />,\n {\n width: size.width,\n height: size.height,\n fonts,\n debug: debug || process.env.NODE_ENV === 'development',\n }\n );\n }\n\n return {\n GET,\n runtime: 'edge' as const,\n };\n}\n\n/**\n * Create OG Image route handler for dynamic route with path parameter\n * \n * This is a convenience wrapper for Next.js dynamic routes like `/api/og/[data]/route.tsx`.\n * It extracts the `data` parameter from the path and passes it to the handler as a query parameter.\n * Also handles static export mode automatically.\n * \n * @example\n * ```tsx\n * // app/api/og/[data]/route.tsx\n * import { createOgImageDynamicRoute } from '@djangocfg/nextjs/og-image';\n * import { OgImageTemplate } from '@/components/OgImageTemplate';\n * \n * export const runtime = 'nodejs';\n * export const dynamic = 'force-static';\n * export const revalidate = false;\n * \n * const handler = createOgImageDynamicRoute({\n * template: OgImageTemplate,\n * defaultProps: {\n * siteName: 'My App',\n * logo: '/logo.svg',\n * },\n * });\n * \n * export async function GET(\n * request: NextRequest,\n * { params }: { params: { data: string } }\n ) {\n * return handler(request, params);\n * }\n * ```\n */\nexport function createOgImageDynamicRoute(config: OgImageHandlerConfig) {\n const handler = createOgImageHandler(config);\n const isStaticBuild = typeof process !== 'undefined' && process.env.NEXT_PUBLIC_STATIC_BUILD === 'true';\n\n async function GET(\n request: NextRequest,\n context: { params: Promise<{ data: string }> }\n ) {\n // In static export mode, return a simple error response\n if (isStaticBuild) {\n return new Response('OG Image generation is not available in static export mode', {\n status: 404,\n headers: { 'Content-Type': 'text/plain' },\n });\n }\n\n // Await params (Next.js 15+ uses Promise)\n const params = await context.params;\n \n // Extract data from path parameter\n const dataParam = params.data;\n \n // Create a request with the data parameter as a query param for the handler\n const url = new URL(request.url);\n url.searchParams.set('data', dataParam);\n \n const modifiedRequest = new NextRequest(url.toString(), {\n method: request.method,\n headers: request.headers,\n });\n\n return handler.GET(modifiedRequest);\n }\n\n // For static export, provide generateStaticParams that returns empty array\n // This allows the route to be excluded from static build\n async function generateStaticParams(): Promise<Array<{ data: string }>> {\n return [];\n }\n\n return {\n GET,\n generateStaticParams,\n };\n}\n","/**\n * Default OG Image Template\n *\n * A modern, gradient-based template for OG images\n */\n\nimport type { ReactElement } from 'react';\nimport type { OgImageTemplateProps } from '../types';\n\n/**\n * Default OG Image Template Component\n *\n * Features:\n * - Modern gradient background\n * - Responsive text sizing\n * - Optional logo and site name\n * - Clean typography\n * - Full customization support\n *\n * @param props - Template props with title, description, siteName, logo and optional customization\n */\nexport function DefaultTemplate({\n title,\n description,\n siteName,\n logo,\n // Visibility flags\n showLogo = true,\n showSiteName = true,\n // Background customization\n backgroundType = 'gradient',\n gradientStart = '#667eea',\n gradientEnd = '#764ba2',\n backgroundColor = '#ffffff',\n // Typography - Title\n titleSize,\n titleWeight = 800,\n titleColor = 'white',\n // Typography - Description\n descriptionSize = 32,\n descriptionColor = 'rgba(255, 255, 255, 0.85)',\n // Typography - Site Name\n siteNameSize = 28,\n siteNameColor = 'rgba(255, 255, 255, 0.95)',\n // Layout\n padding = 80,\n logoSize = 48,\n // Dev mode\n devMode = false,\n}: OgImageTemplateProps): ReactElement {\n // Calculate title size if not provided (responsive based on title length)\n const calculatedTitleSize = titleSize || (title.length > 60 ? 56 : 72);\n \n // Determine background style\n const backgroundStyle =\n backgroundType === 'gradient'\n ? `linear-gradient(135deg, ${gradientStart} 0%, ${gradientEnd} 100%)`\n : backgroundColor;\n\n // Grid overlay for dev mode\n const gridOverlay = devMode ? (\n <div\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundImage: `\n linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px),\n linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px)\n `,\n backgroundSize: '20px 20px',\n pointerEvents: 'none',\n zIndex: 10,\n }}\n />\n ) : null;\n\n return (\n <div\n style={{\n height: '100%',\n width: '100%',\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-start',\n justifyContent: 'space-between',\n background: backgroundStyle,\n padding: `${padding}px`,\n fontFamily: 'system-ui, -apple-system, sans-serif',\n position: 'relative',\n }}\n >\n {gridOverlay}\n \n {/* Header with logo and site name */}\n {((showLogo && logo) || (showSiteName && siteName)) && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: '16px',\n }}\n >\n {showLogo && logo && (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={logo}\n alt=\"Logo\"\n width={logoSize}\n height={logoSize}\n style={{\n borderRadius: '8px',\n }}\n />\n )}\n {showSiteName && siteName && (\n <div\n style={{\n fontSize: siteNameSize,\n fontWeight: 600,\n color: siteNameColor,\n letterSpacing: '-0.02em',\n }}\n >\n {siteName}\n </div>\n )}\n </div>\n )}\n\n {/* Main content */}\n <div\n style={{\n display: 'flex',\n flexDirection: 'column',\n gap: '24px',\n flex: 1,\n justifyContent: 'center',\n }}\n >\n {/* Title */}\n <div\n style={{\n fontSize: calculatedTitleSize,\n fontWeight: titleWeight,\n color: titleColor,\n lineHeight: 1.1,\n letterSpacing: '-0.03em',\n textShadow: backgroundType === 'gradient' ? '0 2px 20px rgba(0, 0, 0, 0.2)' : 'none',\n maxWidth: '100%',\n wordWrap: 'break-word',\n }}\n >\n {title}\n </div>\n\n {/* Description */}\n {description && (\n <div\n style={{\n fontSize: descriptionSize,\n fontWeight: 400,\n color: descriptionColor,\n lineHeight: 1.5,\n letterSpacing: '-0.01em',\n maxWidth: '90%',\n display: '-webkit-box',\n WebkitLineClamp: 2,\n WebkitBoxOrient: 'vertical',\n overflow: 'hidden',\n }}\n >\n {description}\n </div>\n )}\n </div>\n\n {/* Footer decoration */}\n <div\n style={{\n display: 'flex',\n width: '100%',\n height: '4px',\n background: backgroundType === 'gradient' \n ? `linear-gradient(90deg, ${gradientStart} 0%, ${gradientEnd} 100%)`\n : gradientStart,\n borderRadius: '2px',\n }}\n />\n </div>\n );\n}\n\n/**\n * Simple light template variant\n * \n * Light background variant with dark text\n */\nexport function LightTemplate({\n title,\n description,\n siteName,\n logo,\n // Visibility flags\n showLogo = true,\n showSiteName = true,\n // Background customization (defaults to light theme)\n backgroundType = 'solid',\n gradientStart = '#667eea',\n gradientEnd = '#764ba2',\n backgroundColor = '#ffffff',\n // Typography - Title\n titleSize,\n titleWeight = 800,\n titleColor = '#111',\n // Typography - Description\n descriptionSize = 32,\n descriptionColor = '#666',\n // Typography - Site Name\n siteNameSize = 28,\n siteNameColor = '#111',\n // Layout\n padding = 80,\n logoSize = 48,\n // Dev mode\n devMode = false,\n}: OgImageTemplateProps): ReactElement {\n // Calculate title size if not provided (responsive based on title length)\n const calculatedTitleSize = titleSize || (title.length > 60 ? 56 : 72);\n \n // Determine background style\n const backgroundStyle =\n backgroundType === 'gradient'\n ? `linear-gradient(135deg, ${gradientStart} 0%, ${gradientEnd} 100%)`\n : backgroundColor;\n\n // Grid overlay for dev mode\n const gridOverlay = devMode ? (\n <div\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n backgroundImage: `\n linear-gradient(rgba(0, 0, 0, 0.1) 1px, transparent 1px),\n linear-gradient(90deg, rgba(0, 0, 0, 0.1) 1px, transparent 1px)\n `,\n backgroundSize: '20px 20px',\n pointerEvents: 'none',\n zIndex: 10,\n }}\n />\n ) : null;\n\n return (\n <div\n style={{\n height: '100%',\n width: '100%',\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-start',\n justifyContent: 'space-between',\n background: backgroundStyle,\n padding: `${padding}px`,\n fontFamily: 'system-ui, -apple-system, sans-serif',\n position: 'relative',\n }}\n >\n {gridOverlay}\n \n {/* Header with logo and site name */}\n {((showLogo && logo) || (showSiteName && siteName)) && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: '16px',\n }}\n >\n {showLogo && logo && (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={logo}\n alt=\"Logo\"\n width={logoSize}\n height={logoSize}\n style={{\n borderRadius: '8px',\n }}\n />\n )}\n {showSiteName && siteName && (\n <div\n style={{\n fontSize: siteNameSize,\n fontWeight: 600,\n color: siteNameColor,\n letterSpacing: '-0.02em',\n }}\n >\n {siteName}\n </div>\n )}\n </div>\n )}\n\n {/* Main content */}\n <div\n style={{\n display: 'flex',\n flexDirection: 'column',\n gap: '24px',\n flex: 1,\n justifyContent: 'center',\n }}\n >\n {/* Title */}\n <div\n style={{\n fontSize: calculatedTitleSize,\n fontWeight: titleWeight,\n color: titleColor,\n lineHeight: 1.1,\n letterSpacing: '-0.03em',\n maxWidth: '100%',\n wordWrap: 'break-word',\n }}\n >\n {title}\n </div>\n\n {/* Description */}\n {description && (\n <div\n style={{\n fontSize: descriptionSize,\n fontWeight: 400,\n color: descriptionColor,\n lineHeight: 1.5,\n letterSpacing: '-0.01em',\n maxWidth: '90%',\n }}\n >\n {description}\n </div>\n )}\n </div>\n\n {/* Footer decoration */}\n <div\n style={{\n display: 'flex',\n width: '100%',\n height: '4px',\n background: backgroundType === 'gradient' \n ? `linear-gradient(90deg, ${gradientStart} 0%, ${gradientEnd} 100%)`\n : gradientStart,\n borderRadius: '2px',\n }}\n />\n </div>\n );\n}\n\n","/**\n * Font Utilities for OG Image Generation\n *\n * Provides dynamic font loading from Google Fonts without requiring files in public/\n * Based on Vercel's official @vercel/og documentation\n */\n\nexport interface FontConfig {\n name: string;\n weight?: 400 | 500 | 600 | 700 | 800 | 900;\n style?: 'normal' | 'italic';\n data: ArrayBuffer;\n}\n\n/**\n * Load a Google Font dynamically\n *\n * @param font - Font family name (e.g., \"Inter\", \"Roboto\", \"Manrope\")\n * @param text - Text to optimize font for (optional, reduces file size)\n * @param weight - Font weight (default: 700)\n * @returns ArrayBuffer of font data\n *\n * @example\n * const fontData = await loadGoogleFont('Manrope', 'Hello World', 700);\n */\nexport async function loadGoogleFont(\n font: string,\n text?: string,\n weight: number = 700\n): Promise<ArrayBuffer> {\n // Construct Google Fonts API URL\n let url = `https://fonts.googleapis.com/css2?family=${font}:wght@${weight}`;\n\n // Add text parameter to optimize font subset (reduces size)\n if (text) {\n url += `&text=${encodeURIComponent(text)}`;\n }\n\n try {\n // Fetch CSS containing font URL\n const css = await fetch(url, {\n headers: {\n // Required to get TTF format instead of WOFF2\n 'User-Agent':\n 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1',\n },\n }).then((res) => res.text());\n\n // Extract font URL from CSS\n const resource = css.match(/src: url\\((.+)\\) format\\('(opentype|truetype)'\\)/);\n\n if (!resource || !resource[1]) {\n throw new Error(`Failed to parse font URL from CSS for font: ${font}`);\n }\n\n // Fetch actual font file\n const response = await fetch(resource[1]);\n\n if (response.status !== 200) {\n throw new Error(`Failed to fetch font data: HTTP ${response.status}`);\n }\n\n return await response.arrayBuffer();\n } catch (error) {\n console.error(`Error loading Google Font \"${font}\":`, error);\n throw new Error(`Failed to load font \"${font}\": ${error instanceof Error ? error.message : 'Unknown error'}`);\n }\n}\n\n/**\n * Load multiple Google Fonts\n *\n * @param fonts - Array of font configurations to load\n * @returns Array of FontConfig objects ready for ImageResponse\n *\n * @example\n * const fonts = await loadGoogleFonts([\n * { family: 'Manrope', weight: 700 },\n * { family: 'Inter', weight: 400 }\n * ]);\n */\nexport async function loadGoogleFonts(\n fonts: Array<{\n family: string;\n weight?: 400 | 500 | 600 | 700 | 800 | 900;\n style?: 'normal' | 'italic';\n text?: string;\n }>\n): Promise<FontConfig[]> {\n const fontConfigs = await Promise.all(\n fonts.map(async ({ family, weight = 700, style = 'normal', text }) => {\n const data = await loadGoogleFont(family, text, weight);\n return {\n name: family,\n weight,\n style,\n data,\n };\n })\n );\n\n return fontConfigs;\n}\n\n/**\n * Create a font loader with caching\n *\n * Useful for reusing font data across multiple OG image requests\n *\n * @example\n * const fontLoader = createFontLoader();\n * const font = await fontLoader.load('Manrope', 700);\n */\nexport function createFontLoader() {\n const cache = new Map<string, Promise<ArrayBuffer>>();\n\n return {\n /**\n * Load a font with caching\n */\n async load(\n family: string,\n weight: number = 700,\n text?: string\n ): Promise<ArrayBuffer> {\n const cacheKey = `${family}-${weight}-${text || 'all'}`;\n\n if (!cache.has(cacheKey)) {\n cache.set(cacheKey, loadGoogleFont(family, text, weight));\n }\n\n return cache.get(cacheKey)!;\n },\n\n /**\n * Clear the cache\n */\n clear() {\n cache.clear();\n },\n\n /**\n * Get cache size\n */\n size() {\n return cache.size;\n },\n };\n}\n\n","/**\n * URL Generation Helpers for OG Images\n *\n * Utilities to generate OG image URLs with proper query parameters\n */\n\n/** Default OG Image API base URL */\nconst DEFAULT_OG_IMAGE_BASE_URL = 'https://djangocfg.com/api/og';\n\n/**\n * Encode string to base64 with Unicode support\n * Works in both browser and Node.js environments\n */\nfunction encodeBase64(str: string): string {\n // Node.js environment\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(str, 'utf-8').toString('base64');\n }\n // Browser environment - handle Unicode via UTF-8 encoding\n return btoa(unescape(encodeURIComponent(str)));\n}\n\n/**\n * Decode base64 string with Unicode support\n * Works in both browser, Node.js, and Edge Runtime environments\n */\nfunction decodeBase64(str: string): string {\n // Node.js environment\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(str, 'base64').toString('utf-8');\n }\n // Edge Runtime / Browser environment - handle Unicode via UTF-8 decoding\n // atob is available in Edge Runtime\n try {\n const binaryString = atob(str);\n // Convert binary string to UTF-8\n return decodeURIComponent(\n binaryString\n .split('')\n .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join('')\n );\n } catch (error) {\n // Fallback to simpler method if above fails\n return decodeURIComponent(escape(atob(str)));\n }\n}\n\n/**\n * OG Image URL parameters\n * All parameters can be encoded in URL via base64\n */\nexport interface OgImageUrlParams {\n /** Page title */\n title: string;\n /** Page description (optional) */\n description?: string;\n /** Site name (optional) */\n siteName?: string;\n /** Logo URL (optional) */\n logo?: string;\n /** Background type: 'gradient' or 'solid' */\n backgroundType?: 'gradient' | 'solid';\n /** Gradient start color (hex) */\n gradientStart?: string;\n /** Gradient end color (hex) */\n gradientEnd?: string;\n /** Background color (for solid type) */\n backgroundColor?: string;\n /** Title font size (px) */\n titleSize?: number;\n /** Title font weight */\n titleWeight?: number;\n /** Title text color */\n titleColor?: string;\n /** Description font size (px) */\n descriptionSize?: number;\n /** Description text color */\n descriptionColor?: string;\n /** Site name font size (px) */\n siteNameSize?: number;\n /** Site name text color */\n siteNameColor?: string;\n /** Padding (px) */\n padding?: number;\n /** Logo size (px) */\n logoSize?: number;\n /** Show logo flag */\n showLogo?: boolean;\n /** Show site name flag */\n showSiteName?: boolean;\n /** Additional custom parameters */\n [key: string]: string | number | boolean | undefined;\n}\n\n/**\n * Options for generating OG image URL\n */\nexport interface GenerateOgImageUrlOptions {\n /**\n * Base URL of the OG image API route\n * @default 'https://djangocfg.com/api/og'\n */\n baseUrl?: string;\n /**\n * If true, encode params as base64 for safer URLs\n * @default true\n */\n useBase64?: boolean;\n}\n\n/**\n * Generate OG image URL with query parameters or base64 encoding\n *\n * @param params - URL parameters for the OG image\n * @param options - Generation options (baseUrl, useBase64)\n * @returns Complete OG image URL with encoded parameters\n *\n * @example\n * ```typescript\n * // Using default baseUrl (https://djangocfg.com/api/og)\n * const url = generateOgImageUrl({\n * title: 'My Page Title',\n * description: 'Page description here',\n * });\n *\n * // With custom baseUrl\n * const url = generateOgImageUrl(\n * { title: 'My Page' },\n * { baseUrl: '/api/og' }\n * );\n * ```\n */\nexport function generateOgImageUrl(\n params: OgImageUrlParams,\n options: GenerateOgImageUrlOptions = {}\n): string {\n const {\n baseUrl = DEFAULT_OG_IMAGE_BASE_URL,\n useBase64 = true\n } = options;\n\n if (useBase64) {\n // Clean params - remove undefined/null/empty values\n const cleanParams: Record<string, string | number | boolean> = {};\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null && value !== '') {\n cleanParams[key] = value;\n }\n });\n\n // Encode as base64 (Unicode-safe)\n const jsonString = JSON.stringify(cleanParams);\n const base64Data = encodeBase64(jsonString);\n\n // CRITICAL: Use path parameter instead of query parameter\n // Next.js strips query params in internal requests for metadata generation\n // Using /api/og/[data] instead of /api/og?data=... preserves the data\n // IMPORTANT: Add trailing slash to avoid 308 redirects which cause timeouts in crawlers\n return `${baseUrl}/${base64Data}/`;\n } else {\n // Legacy query params mode\n const searchParams = new URLSearchParams();\n\n // Add all defined parameters\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null && value !== '') {\n searchParams.append(key, String(value));\n }\n });\n\n const query = searchParams.toString();\n return query ? `${baseUrl}?${query}` : baseUrl;\n }\n}\n\n/**\n * Get absolute OG image URL from relative path\n *\n * Useful for generating absolute URLs required by Open Graph meta tags\n *\n * @param relativePath - Relative OG image path (e.g., '/api/og?title=Hello')\n * @param siteUrl - Base site URL (e.g., 'https://example.com')\n * @returns Absolute URL\n *\n * @example\n * ```typescript\n * const absolute = getAbsoluteOgImageUrl(\n * '/api/og?title=Hello',\n * 'https://example.com'\n * );\n * // Result: https://example.com/api/og?title=Hello\n * ```\n */\nexport function getAbsoluteOgImageUrl(\n relativePath: string,\n siteUrl: string\n): string {\n // If path is already an absolute URL, return as-is\n if (relativePath.startsWith('http://') || relativePath.startsWith('https://')) {\n return relativePath;\n }\n\n // Remove trailing slash from site URL\n const cleanSiteUrl = siteUrl.replace(/\\/$/, '');\n\n // Ensure relative path starts with /\n const cleanPath = relativePath.startsWith('/')\n ? relativePath\n : `/${relativePath}`;\n\n return `${cleanSiteUrl}${cleanPath}`;\n}\n\n/**\n * Create OG image URL builder with preset configuration\n *\n * Useful when you want to reuse the same base URL and default parameters\n *\n * @param defaults - Default parameters to merge with each URL generation\n * @param options - Default options (baseUrl, useBase64)\n * @returns URL builder function\n *\n * @example\n * ```typescript\n * const buildOgUrl = createOgImageUrlBuilder(\n * { siteName: 'My Site', logo: '/logo.png' },\n * { baseUrl: '/api/og' }\n * );\n *\n * const url1 = buildOgUrl({ title: 'Page 1' });\n * const url2 = buildOgUrl({ title: 'Page 2', description: 'Custom desc' });\n * ```\n */\nexport function createOgImageUrlBuilder(\n defaults: Partial<OgImageUrlParams> = {},\n options: GenerateOgImageUrlOptions = {}\n) {\n return (params: OgImageUrlParams): string => {\n return generateOgImageUrl(\n { ...defaults, ...params },\n options\n );\n };\n}\n\n/**\n * Parse OG image URL parameters from a URL string (legacy query params)\n *\n * @param url - Full or relative URL with query parameters\n * @returns Parsed parameters object\n *\n * @example\n * ```typescript\n * const params = parseOgImageUrl('/api/og?title=Hello&description=World');\n * // Result: { title: 'Hello', description: 'World' }\n * ```\n */\nexport function parseOgImageUrl(url: string): Record<string, string> {\n try {\n const urlObj = new URL(url, 'http://dummy.com');\n const params: Record<string, string> = {};\n\n urlObj.searchParams.forEach((value, key) => {\n params[key] = value;\n });\n\n return params;\n } catch {\n return {};\n }\n}\n\n/**\n * Parse OG image data from base64-encoded query parameter\n *\n * Use this in your API route to decode the `data` parameter\n * Supports both base64 (new) and legacy query params format\n *\n * @param searchParams - URL search params or request object\n * @returns Parsed OG image parameters\n *\n * @example\n * ```typescript\n * // In Next.js API route (pages/api/og.ts)\n * export default function handler(req) {\n * const params = parseOgImageData(req.query);\n * // { title: 'Hello', description: 'World' }\n * }\n *\n * // In Next.js App Router (app/api/og/route.ts)\n * export async function GET(request: Request) {\n * const { searchParams } = new URL(request.url);\n * const params = parseOgImageData(Object.fromEntries(searchParams));\n * // { title: 'Hello', description: 'World' }\n * }\n * ```\n */\nexport function parseOgImageData(\n searchParams: Record<string, string | string[] | undefined> | URLSearchParams\n): Record<string, string> {\n try {\n // Handle URLSearchParams\n let params: Record<string, string | undefined>;\n\n if (searchParams instanceof URLSearchParams) {\n // Convert URLSearchParams to object\n params = {};\n for (const [key, value] of searchParams.entries()) {\n params[key] = value;\n }\n } else {\n params = searchParams as Record<string, string | undefined>;\n }\n\n // Debug logging\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Input params keys:', Object.keys(params));\n console.log('[parseOgImageData] Input params:', params);\n }\n\n // Check for base64-encoded data parameter\n const dataParam = params.data;\n if (dataParam && typeof dataParam === 'string' && dataParam.trim() !== '') {\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Found data param, length:', dataParam.length);\n }\n\n try {\n const decoded = decodeBase64(dataParam);\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Decoded string:', decoded.substring(0, 100));\n }\n\n const parsed = JSON.parse(decoded);\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Parsed JSON:', parsed);\n }\n\n // Ensure all values are strings\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(parsed)) {\n if (value !== undefined && value !== null) {\n result[key] = String(value);\n }\n }\n\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Result:', result);\n }\n\n return result;\n } catch (decodeError) {\n console.error('[parseOgImageData] Error decoding/parsing data param:', decodeError);\n if (decodeError instanceof Error) {\n console.error('[parseOgImageData] Error message:', decodeError.message);\n }\n // Fall through to legacy query params\n }\n } else {\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] No data param found or empty');\n }\n }\n\n // Fallback to legacy query params format\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(params)) {\n if (key !== 'data' && value !== undefined && value !== null) {\n result[key] = Array.isArray(value) ? value[0] : String(value);\n }\n }\n\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Fallback result:', result);\n }\n\n return result;\n } catch (error) {\n console.error('[parseOgImageData] Unexpected error:', error);\n return {};\n }\n}\n\n// Export base64 utilities for advanced use cases\nexport { encodeBase64, decodeBase64 };\n","/**\n * Metadata Utilities for OG Images\n *\n * Helpers to automatically add og:image to Next.js metadata\n */\n\nimport type { Metadata } from 'next';\nimport { generateOgImageUrl, getAbsoluteOgImageUrl, OgImageUrlParams} from './url';\n\n/**\n * Options for generating OG image metadata\n */\nexport interface OgImageMetadataOptions {\n /** Base URL of the OG image API route (e.g., '/api/og') */\n ogImageBaseUrl?: string;\n /** Site URL for absolute URLs (e.g., 'https://example.com') */\n siteUrl?: string;\n /** Default parameters to merge with page-specific params */\n defaultParams?: Partial<OgImageUrlParams>;\n /** Whether to use base64 encoding (default: true) */\n useBase64?: boolean;\n}\n\n/**\n * Extract title from metadata\n */\nfunction extractTitle(metadata: Metadata): string {\n if (typeof metadata.title === 'string') {\n return metadata.title;\n }\n if (metadata.title) {\n if ('default' in metadata.title) {\n return metadata.title.default;\n }\n if ('absolute' in metadata.title) {\n return metadata.title.absolute;\n }\n }\n return '';\n}\n\n/**\n * Extract description from metadata\n */\nfunction extractDescription(metadata: Metadata): string {\n if (typeof metadata.description === 'string') {\n return metadata.description;\n }\n return '';\n}\n\n/**\n * Generate Next.js metadata with OG image\n *\n * Automatically adds og:image, twitter:image, and other OG meta tags\n * Automatically extracts title and description from metadata if not provided\n *\n * @param metadata - Base metadata object\n * @param ogImageParams - Optional parameters for OG image generation (if not provided, extracted from metadata)\n * @param options - Configuration options\n * @returns Enhanced metadata with OG image\n *\n * @example\n * ```typescript\n * // In page.tsx or layout.tsx\n * import { generateOgImageMetadata } from '@djangocfg/nextjs/og-image';\n * import { settings } from '@/core/settings';\n *\n * export const metadata = generateOgImageMetadata(\n * {\n * title: 'My Page',\n * description: 'Page description',\n * },\n * undefined, // Will auto-extract from metadata\n * {\n * ogImageBaseUrl: '/api/og',\n * siteUrl: settings.app.siteUrl,\n * defaultParams: {\n * siteName: settings.app.name,\n * logo: settings.app.icons.logoVector,\n * },\n * }\n * );\n * ```\n */\n/**\n * Get site URL automatically from environment\n * Priority: NEXT_PUBLIC_SITE_URL > VERCEL_URL > fallback\n */\nfunction getSiteUrl(): string {\n // Try NEXT_PUBLIC_SITE_URL first (most reliable)\n if (typeof process !== 'undefined' && process.env.NEXT_PUBLIC_SITE_URL) {\n return process.env.NEXT_PUBLIC_SITE_URL;\n }\n \n // Development fallback\n return '';\n}\n\nexport function generateOgImageMetadata(\n metadata: Metadata,\n ogImageParams?: Partial<OgImageUrlParams>,\n options: OgImageMetadataOptions = {}\n): Metadata {\n const {\n ogImageBaseUrl = 'https://djangocfg.com/api/og',\n siteUrl: providedSiteUrl,\n defaultParams = {},\n useBase64 = true,\n } = options;\n\n // Automatically determine siteUrl if not provided or is undefined\n const siteUrl = providedSiteUrl && providedSiteUrl !== 'undefined' \n ? providedSiteUrl \n : getSiteUrl();\n\n // Auto-extract title and description from metadata if not provided\n const extractedTitle = extractTitle(metadata);\n const extractedDescription = extractDescription(metadata);\n\n // Merge with provided params (provided params take precedence)\n const finalOgImageParams: OgImageUrlParams = {\n ...defaultParams,\n title: ogImageParams?.title || extractedTitle || defaultParams.title || '',\n description: ogImageParams?.description || extractedDescription || defaultParams.description || '',\n ...ogImageParams,\n };\n\n // Get alt text for image (title or siteName as fallback)\n const imageAlt = finalOgImageParams.title || finalOgImageParams.siteName;\n\n // Generate relative OG image URL\n const relativeOgImageUrl = generateOgImageUrl(\n finalOgImageParams,\n { baseUrl: ogImageBaseUrl, useBase64 }\n );\n\n // CRITICAL: Use absolute URL to ensure query params are preserved\n // Next.js might strip query params from relative URLs in some cases\n // Absolute URLs ensure the full URL with params is used\n const ogImageUrl = siteUrl\n ? getAbsoluteOgImageUrl(relativeOgImageUrl, siteUrl)\n : relativeOgImageUrl;\n\n // Normalize existing images to arrays\n const existingOgImages = metadata.openGraph?.images\n ? Array.isArray(metadata.openGraph.images)\n ? metadata.openGraph.images\n : [metadata.openGraph.images]\n : [];\n\n const existingTwitterImages = metadata.twitter?.images\n ? Array.isArray(metadata.twitter.images)\n ? metadata.twitter.images\n : [metadata.twitter.images]\n : [];\n\n // Build final metadata object\n const finalMetadata: Metadata = {\n ...metadata,\n openGraph: {\n ...metadata.openGraph,\n images: [\n ...existingOgImages,\n {\n url: ogImageUrl,\n width: 1200,\n height: 630,\n alt: imageAlt,\n },\n ],\n },\n twitter: {\n ...metadata.twitter,\n card: 'summary_large_image',\n images: [\n ...existingTwitterImages,\n {\n url: ogImageUrl,\n alt: imageAlt,\n },\n ],\n },\n };\n\n // Automatically add metadataBase if siteUrl is an absolute URL\n // metadataBase requires absolute URL - skip if siteUrl is relative path (like /cfg/admin)\n // Only add if not already set in input metadata\n if (!finalMetadata.metadataBase && siteUrl) {\n // Check if siteUrl is an absolute URL (starts with http:// or https://)\n if (siteUrl.startsWith('http://') || siteUrl.startsWith('https://')) {\n try {\n finalMetadata.metadataBase = new URL(siteUrl);\n } catch (e) {\n // If URL construction fails, skip metadataBase\n // This shouldn't happen if we check for http/https, but just in case\n }\n }\n }\n\n return finalMetadata;\n}\n\n/**\n * Create OG image metadata generator with preset configuration\n *\n * Useful when you want to reuse the same configuration across multiple pages\n *\n * @param options - Configuration options\n * @returns Metadata generator function\n *\n * @example\n * ```typescript\n * // In a shared file (e.g., lib/metadata.ts)\n * import { createOgImageMetadataGenerator } from '@djangocfg/nextjs/og-image';\n * import { settings } from '@/core/settings';\n *\n * export const generateMetadata = createOgImageMetadataGenerator({\n * ogImageBaseUrl: '/api/og',\n * siteUrl: settings.app.siteUrl,\n * defaultParams: {\n * siteName: settings.app.name,\n * logo: settings.app.icons.logoVector,\n * },\n * });\n *\n * // In page.tsx\n * import { generateMetadata } from '@/lib/metadata';\n *\n * export const metadata = generateMetadata({\n * title: 'My Page',\n * description: 'Description',\n * });\n * ```\n */\nexport function createOgImageMetadataGenerator(\n options: OgImageMetadataOptions\n) {\n return (\n metadata: Metadata,\n ogImageParams?: Partial<OgImageUrlParams>\n ): Metadata => {\n return generateOgImageMetadata(metadata, ogImageParams, options);\n };\n}\n\n"],"mappings":"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","names":["result","jsx"]}
@@ -102,7 +102,7 @@ function generateOgImageUrl(params, options = {}) {
102
102
  });
103
103
  const jsonString = JSON.stringify(cleanParams);
104
104
  const base64Data = encodeBase64(jsonString);
105
- return `${baseUrl}/${base64Data}`;
105
+ return `${baseUrl}/${base64Data}/`;
106
106
  } else {
107
107
  const searchParams = new URLSearchParams();
108
108
  Object.entries(params).forEach(([key, value]) => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/og-image/utils/fonts.ts","../../../src/og-image/utils/url.ts","../../../src/og-image/utils/metadata.ts"],"sourcesContent":["/**\n * Font Utilities for OG Image Generation\n *\n * Provides dynamic font loading from Google Fonts without requiring files in public/\n * Based on Vercel's official @vercel/og documentation\n */\n\nexport interface FontConfig {\n name: string;\n weight?: 400 | 500 | 600 | 700 | 800 | 900;\n style?: 'normal' | 'italic';\n data: ArrayBuffer;\n}\n\n/**\n * Load a Google Font dynamically\n *\n * @param font - Font family name (e.g., \"Inter\", \"Roboto\", \"Manrope\")\n * @param text - Text to optimize font for (optional, reduces file size)\n * @param weight - Font weight (default: 700)\n * @returns ArrayBuffer of font data\n *\n * @example\n * const fontData = await loadGoogleFont('Manrope', 'Hello World', 700);\n */\nexport async function loadGoogleFont(\n font: string,\n text?: string,\n weight: number = 700\n): Promise<ArrayBuffer> {\n // Construct Google Fonts API URL\n let url = `https://fonts.googleapis.com/css2?family=${font}:wght@${weight}`;\n\n // Add text parameter to optimize font subset (reduces size)\n if (text) {\n url += `&text=${encodeURIComponent(text)}`;\n }\n\n try {\n // Fetch CSS containing font URL\n const css = await fetch(url, {\n headers: {\n // Required to get TTF format instead of WOFF2\n 'User-Agent':\n 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1',\n },\n }).then((res) => res.text());\n\n // Extract font URL from CSS\n const resource = css.match(/src: url\\((.+)\\) format\\('(opentype|truetype)'\\)/);\n\n if (!resource || !resource[1]) {\n throw new Error(`Failed to parse font URL from CSS for font: ${font}`);\n }\n\n // Fetch actual font file\n const response = await fetch(resource[1]);\n\n if (response.status !== 200) {\n throw new Error(`Failed to fetch font data: HTTP ${response.status}`);\n }\n\n return await response.arrayBuffer();\n } catch (error) {\n console.error(`Error loading Google Font \"${font}\":`, error);\n throw new Error(`Failed to load font \"${font}\": ${error instanceof Error ? error.message : 'Unknown error'}`);\n }\n}\n\n/**\n * Load multiple Google Fonts\n *\n * @param fonts - Array of font configurations to load\n * @returns Array of FontConfig objects ready for ImageResponse\n *\n * @example\n * const fonts = await loadGoogleFonts([\n * { family: 'Manrope', weight: 700 },\n * { family: 'Inter', weight: 400 }\n * ]);\n */\nexport async function loadGoogleFonts(\n fonts: Array<{\n family: string;\n weight?: 400 | 500 | 600 | 700 | 800 | 900;\n style?: 'normal' | 'italic';\n text?: string;\n }>\n): Promise<FontConfig[]> {\n const fontConfigs = await Promise.all(\n fonts.map(async ({ family, weight = 700, style = 'normal', text }) => {\n const data = await loadGoogleFont(family, text, weight);\n return {\n name: family,\n weight,\n style,\n data,\n };\n })\n );\n\n return fontConfigs;\n}\n\n/**\n * Create a font loader with caching\n *\n * Useful for reusing font data across multiple OG image requests\n *\n * @example\n * const fontLoader = createFontLoader();\n * const font = await fontLoader.load('Manrope', 700);\n */\nexport function createFontLoader() {\n const cache = new Map<string, Promise<ArrayBuffer>>();\n\n return {\n /**\n * Load a font with caching\n */\n async load(\n family: string,\n weight: number = 700,\n text?: string\n ): Promise<ArrayBuffer> {\n const cacheKey = `${family}-${weight}-${text || 'all'}`;\n\n if (!cache.has(cacheKey)) {\n cache.set(cacheKey, loadGoogleFont(family, text, weight));\n }\n\n return cache.get(cacheKey)!;\n },\n\n /**\n * Clear the cache\n */\n clear() {\n cache.clear();\n },\n\n /**\n * Get cache size\n */\n size() {\n return cache.size;\n },\n };\n}\n\n","/**\n * URL Generation Helpers for OG Images\n *\n * Utilities to generate OG image URLs with proper query parameters\n */\n\n/** Default OG Image API base URL */\nconst DEFAULT_OG_IMAGE_BASE_URL = 'https://djangocfg.com/api/og';\n\n/**\n * Encode string to base64 with Unicode support\n * Works in both browser and Node.js environments\n */\nfunction encodeBase64(str: string): string {\n // Node.js environment\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(str, 'utf-8').toString('base64');\n }\n // Browser environment - handle Unicode via UTF-8 encoding\n return btoa(unescape(encodeURIComponent(str)));\n}\n\n/**\n * Decode base64 string with Unicode support\n * Works in both browser, Node.js, and Edge Runtime environments\n */\nfunction decodeBase64(str: string): string {\n // Node.js environment\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(str, 'base64').toString('utf-8');\n }\n // Edge Runtime / Browser environment - handle Unicode via UTF-8 decoding\n // atob is available in Edge Runtime\n try {\n const binaryString = atob(str);\n // Convert binary string to UTF-8\n return decodeURIComponent(\n binaryString\n .split('')\n .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join('')\n );\n } catch (error) {\n // Fallback to simpler method if above fails\n return decodeURIComponent(escape(atob(str)));\n }\n}\n\n/**\n * OG Image URL parameters\n * All parameters can be encoded in URL via base64\n */\nexport interface OgImageUrlParams {\n /** Page title */\n title: string;\n /** Page description (optional) */\n description?: string;\n /** Site name (optional) */\n siteName?: string;\n /** Logo URL (optional) */\n logo?: string;\n /** Background type: 'gradient' or 'solid' */\n backgroundType?: 'gradient' | 'solid';\n /** Gradient start color (hex) */\n gradientStart?: string;\n /** Gradient end color (hex) */\n gradientEnd?: string;\n /** Background color (for solid type) */\n backgroundColor?: string;\n /** Title font size (px) */\n titleSize?: number;\n /** Title font weight */\n titleWeight?: number;\n /** Title text color */\n titleColor?: string;\n /** Description font size (px) */\n descriptionSize?: number;\n /** Description text color */\n descriptionColor?: string;\n /** Site name font size (px) */\n siteNameSize?: number;\n /** Site name text color */\n siteNameColor?: string;\n /** Padding (px) */\n padding?: number;\n /** Logo size (px) */\n logoSize?: number;\n /** Show logo flag */\n showLogo?: boolean;\n /** Show site name flag */\n showSiteName?: boolean;\n /** Additional custom parameters */\n [key: string]: string | number | boolean | undefined;\n}\n\n/**\n * Options for generating OG image URL\n */\nexport interface GenerateOgImageUrlOptions {\n /**\n * Base URL of the OG image API route\n * @default 'https://djangocfg.com/api/og'\n */\n baseUrl?: string;\n /**\n * If true, encode params as base64 for safer URLs\n * @default true\n */\n useBase64?: boolean;\n}\n\n/**\n * Generate OG image URL with query parameters or base64 encoding\n *\n * @param params - URL parameters for the OG image\n * @param options - Generation options (baseUrl, useBase64)\n * @returns Complete OG image URL with encoded parameters\n *\n * @example\n * ```typescript\n * // Using default baseUrl (https://djangocfg.com/api/og)\n * const url = generateOgImageUrl({\n * title: 'My Page Title',\n * description: 'Page description here',\n * });\n *\n * // With custom baseUrl\n * const url = generateOgImageUrl(\n * { title: 'My Page' },\n * { baseUrl: '/api/og' }\n * );\n * ```\n */\nexport function generateOgImageUrl(\n params: OgImageUrlParams,\n options: GenerateOgImageUrlOptions = {}\n): string {\n const {\n baseUrl = DEFAULT_OG_IMAGE_BASE_URL,\n useBase64 = true\n } = options;\n\n if (useBase64) {\n // Clean params - remove undefined/null/empty values\n const cleanParams: Record<string, string | number | boolean> = {};\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null && value !== '') {\n cleanParams[key] = value;\n }\n });\n\n // Encode as base64 (Unicode-safe)\n const jsonString = JSON.stringify(cleanParams);\n const base64Data = encodeBase64(jsonString);\n\n // CRITICAL: Use path parameter instead of query parameter\n // Next.js strips query params in internal requests for metadata generation\n // Using /api/og/[data] instead of /api/og?data=... preserves the data\n return `${baseUrl}/${base64Data}`;\n } else {\n // Legacy query params mode\n const searchParams = new URLSearchParams();\n\n // Add all defined parameters\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null && value !== '') {\n searchParams.append(key, String(value));\n }\n });\n\n const query = searchParams.toString();\n return query ? `${baseUrl}?${query}` : baseUrl;\n }\n}\n\n/**\n * Get absolute OG image URL from relative path\n *\n * Useful for generating absolute URLs required by Open Graph meta tags\n *\n * @param relativePath - Relative OG image path (e.g., '/api/og?title=Hello')\n * @param siteUrl - Base site URL (e.g., 'https://example.com')\n * @returns Absolute URL\n *\n * @example\n * ```typescript\n * const absolute = getAbsoluteOgImageUrl(\n * '/api/og?title=Hello',\n * 'https://example.com'\n * );\n * // Result: https://example.com/api/og?title=Hello\n * ```\n */\nexport function getAbsoluteOgImageUrl(\n relativePath: string,\n siteUrl: string\n): string {\n // If path is already an absolute URL, return as-is\n if (relativePath.startsWith('http://') || relativePath.startsWith('https://')) {\n return relativePath;\n }\n\n // Remove trailing slash from site URL\n const cleanSiteUrl = siteUrl.replace(/\\/$/, '');\n\n // Ensure relative path starts with /\n const cleanPath = relativePath.startsWith('/')\n ? relativePath\n : `/${relativePath}`;\n\n return `${cleanSiteUrl}${cleanPath}`;\n}\n\n/**\n * Create OG image URL builder with preset configuration\n *\n * Useful when you want to reuse the same base URL and default parameters\n *\n * @param defaults - Default parameters to merge with each URL generation\n * @param options - Default options (baseUrl, useBase64)\n * @returns URL builder function\n *\n * @example\n * ```typescript\n * const buildOgUrl = createOgImageUrlBuilder(\n * { siteName: 'My Site', logo: '/logo.png' },\n * { baseUrl: '/api/og' }\n * );\n *\n * const url1 = buildOgUrl({ title: 'Page 1' });\n * const url2 = buildOgUrl({ title: 'Page 2', description: 'Custom desc' });\n * ```\n */\nexport function createOgImageUrlBuilder(\n defaults: Partial<OgImageUrlParams> = {},\n options: GenerateOgImageUrlOptions = {}\n) {\n return (params: OgImageUrlParams): string => {\n return generateOgImageUrl(\n { ...defaults, ...params },\n options\n );\n };\n}\n\n/**\n * Parse OG image URL parameters from a URL string (legacy query params)\n *\n * @param url - Full or relative URL with query parameters\n * @returns Parsed parameters object\n *\n * @example\n * ```typescript\n * const params = parseOgImageUrl('/api/og?title=Hello&description=World');\n * // Result: { title: 'Hello', description: 'World' }\n * ```\n */\nexport function parseOgImageUrl(url: string): Record<string, string> {\n try {\n const urlObj = new URL(url, 'http://dummy.com');\n const params: Record<string, string> = {};\n\n urlObj.searchParams.forEach((value, key) => {\n params[key] = value;\n });\n\n return params;\n } catch {\n return {};\n }\n}\n\n/**\n * Parse OG image data from base64-encoded query parameter\n *\n * Use this in your API route to decode the `data` parameter\n * Supports both base64 (new) and legacy query params format\n *\n * @param searchParams - URL search params or request object\n * @returns Parsed OG image parameters\n *\n * @example\n * ```typescript\n * // In Next.js API route (pages/api/og.ts)\n * export default function handler(req) {\n * const params = parseOgImageData(req.query);\n * // { title: 'Hello', description: 'World' }\n * }\n *\n * // In Next.js App Router (app/api/og/route.ts)\n * export async function GET(request: Request) {\n * const { searchParams } = new URL(request.url);\n * const params = parseOgImageData(Object.fromEntries(searchParams));\n * // { title: 'Hello', description: 'World' }\n * }\n * ```\n */\nexport function parseOgImageData(\n searchParams: Record<string, string | string[] | undefined> | URLSearchParams\n): Record<string, string> {\n try {\n // Handle URLSearchParams\n let params: Record<string, string | undefined>;\n\n if (searchParams instanceof URLSearchParams) {\n // Convert URLSearchParams to object\n params = {};\n for (const [key, value] of searchParams.entries()) {\n params[key] = value;\n }\n } else {\n params = searchParams as Record<string, string | undefined>;\n }\n\n // Debug logging\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Input params keys:', Object.keys(params));\n console.log('[parseOgImageData] Input params:', params);\n }\n\n // Check for base64-encoded data parameter\n const dataParam = params.data;\n if (dataParam && typeof dataParam === 'string' && dataParam.trim() !== '') {\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Found data param, length:', dataParam.length);\n }\n\n try {\n const decoded = decodeBase64(dataParam);\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Decoded string:', decoded.substring(0, 100));\n }\n\n const parsed = JSON.parse(decoded);\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Parsed JSON:', parsed);\n }\n\n // Ensure all values are strings\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(parsed)) {\n if (value !== undefined && value !== null) {\n result[key] = String(value);\n }\n }\n\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Result:', result);\n }\n\n return result;\n } catch (decodeError) {\n console.error('[parseOgImageData] Error decoding/parsing data param:', decodeError);\n if (decodeError instanceof Error) {\n console.error('[parseOgImageData] Error message:', decodeError.message);\n }\n // Fall through to legacy query params\n }\n } else {\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] No data param found or empty');\n }\n }\n\n // Fallback to legacy query params format\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(params)) {\n if (key !== 'data' && value !== undefined && value !== null) {\n result[key] = Array.isArray(value) ? value[0] : String(value);\n }\n }\n\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Fallback result:', result);\n }\n\n return result;\n } catch (error) {\n console.error('[parseOgImageData] Unexpected error:', error);\n return {};\n }\n}\n\n// Export base64 utilities for advanced use cases\nexport { encodeBase64, decodeBase64 };\n","/**\n * Metadata Utilities for OG Images\n *\n * Helpers to automatically add og:image to Next.js metadata\n */\n\nimport type { Metadata } from 'next';\nimport { generateOgImageUrl, getAbsoluteOgImageUrl, OgImageUrlParams} from './url';\n\n/**\n * Options for generating OG image metadata\n */\nexport interface OgImageMetadataOptions {\n /** Base URL of the OG image API route (e.g., '/api/og') */\n ogImageBaseUrl?: string;\n /** Site URL for absolute URLs (e.g., 'https://example.com') */\n siteUrl?: string;\n /** Default parameters to merge with page-specific params */\n defaultParams?: Partial<OgImageUrlParams>;\n /** Whether to use base64 encoding (default: true) */\n useBase64?: boolean;\n}\n\n/**\n * Extract title from metadata\n */\nfunction extractTitle(metadata: Metadata): string {\n if (typeof metadata.title === 'string') {\n return metadata.title;\n }\n if (metadata.title) {\n if ('default' in metadata.title) {\n return metadata.title.default;\n }\n if ('absolute' in metadata.title) {\n return metadata.title.absolute;\n }\n }\n return '';\n}\n\n/**\n * Extract description from metadata\n */\nfunction extractDescription(metadata: Metadata): string {\n if (typeof metadata.description === 'string') {\n return metadata.description;\n }\n return '';\n}\n\n/**\n * Generate Next.js metadata with OG image\n *\n * Automatically adds og:image, twitter:image, and other OG meta tags\n * Automatically extracts title and description from metadata if not provided\n *\n * @param metadata - Base metadata object\n * @param ogImageParams - Optional parameters for OG image generation (if not provided, extracted from metadata)\n * @param options - Configuration options\n * @returns Enhanced metadata with OG image\n *\n * @example\n * ```typescript\n * // In page.tsx or layout.tsx\n * import { generateOgImageMetadata } from '@djangocfg/nextjs/og-image';\n * import { settings } from '@/core/settings';\n *\n * export const metadata = generateOgImageMetadata(\n * {\n * title: 'My Page',\n * description: 'Page description',\n * },\n * undefined, // Will auto-extract from metadata\n * {\n * ogImageBaseUrl: '/api/og',\n * siteUrl: settings.app.siteUrl,\n * defaultParams: {\n * siteName: settings.app.name,\n * logo: settings.app.icons.logoVector,\n * },\n * }\n * );\n * ```\n */\n/**\n * Get site URL automatically from environment\n * Priority: NEXT_PUBLIC_SITE_URL > VERCEL_URL > fallback\n */\nfunction getSiteUrl(): string {\n // Try NEXT_PUBLIC_SITE_URL first (most reliable)\n if (typeof process !== 'undefined' && process.env.NEXT_PUBLIC_SITE_URL) {\n return process.env.NEXT_PUBLIC_SITE_URL;\n }\n \n // Development fallback\n return '';\n}\n\nexport function generateOgImageMetadata(\n metadata: Metadata,\n ogImageParams?: Partial<OgImageUrlParams>,\n options: OgImageMetadataOptions = {}\n): Metadata {\n const {\n ogImageBaseUrl = 'https://djangocfg.com/api/og',\n siteUrl: providedSiteUrl,\n defaultParams = {},\n useBase64 = true,\n } = options;\n\n // Automatically determine siteUrl if not provided or is undefined\n const siteUrl = providedSiteUrl && providedSiteUrl !== 'undefined' \n ? providedSiteUrl \n : getSiteUrl();\n\n // Auto-extract title and description from metadata if not provided\n const extractedTitle = extractTitle(metadata);\n const extractedDescription = extractDescription(metadata);\n\n // Merge with provided params (provided params take precedence)\n const finalOgImageParams: OgImageUrlParams = {\n ...defaultParams,\n title: ogImageParams?.title || extractedTitle || defaultParams.title || '',\n description: ogImageParams?.description || extractedDescription || defaultParams.description || '',\n ...ogImageParams,\n };\n\n // Get alt text for image (title or siteName as fallback)\n const imageAlt = finalOgImageParams.title || finalOgImageParams.siteName;\n\n // Generate relative OG image URL\n const relativeOgImageUrl = generateOgImageUrl(\n finalOgImageParams,\n { baseUrl: ogImageBaseUrl, useBase64 }\n );\n\n // CRITICAL: Use absolute URL to ensure query params are preserved\n // Next.js might strip query params from relative URLs in some cases\n // Absolute URLs ensure the full URL with params is used\n const ogImageUrl = siteUrl\n ? getAbsoluteOgImageUrl(relativeOgImageUrl, siteUrl)\n : relativeOgImageUrl;\n\n // Normalize existing images to arrays\n const existingOgImages = metadata.openGraph?.images\n ? Array.isArray(metadata.openGraph.images)\n ? metadata.openGraph.images\n : [metadata.openGraph.images]\n : [];\n\n const existingTwitterImages = metadata.twitter?.images\n ? Array.isArray(metadata.twitter.images)\n ? metadata.twitter.images\n : [metadata.twitter.images]\n : [];\n\n // Build final metadata object\n const finalMetadata: Metadata = {\n ...metadata,\n openGraph: {\n ...metadata.openGraph,\n images: [\n ...existingOgImages,\n {\n url: ogImageUrl,\n width: 1200,\n height: 630,\n alt: imageAlt,\n },\n ],\n },\n twitter: {\n ...metadata.twitter,\n card: 'summary_large_image',\n images: [\n ...existingTwitterImages,\n {\n url: ogImageUrl,\n alt: imageAlt,\n },\n ],\n },\n };\n\n // Automatically add metadataBase if siteUrl is an absolute URL\n // metadataBase requires absolute URL - skip if siteUrl is relative path (like /cfg/admin)\n // Only add if not already set in input metadata\n if (!finalMetadata.metadataBase && siteUrl) {\n // Check if siteUrl is an absolute URL (starts with http:// or https://)\n if (siteUrl.startsWith('http://') || siteUrl.startsWith('https://')) {\n try {\n finalMetadata.metadataBase = new URL(siteUrl);\n } catch (e) {\n // If URL construction fails, skip metadataBase\n // This shouldn't happen if we check for http/https, but just in case\n }\n }\n }\n\n return finalMetadata;\n}\n\n/**\n * Create OG image metadata generator with preset configuration\n *\n * Useful when you want to reuse the same configuration across multiple pages\n *\n * @param options - Configuration options\n * @returns Metadata generator function\n *\n * @example\n * ```typescript\n * // In a shared file (e.g., lib/metadata.ts)\n * import { createOgImageMetadataGenerator } from '@djangocfg/nextjs/og-image';\n * import { settings } from '@/core/settings';\n *\n * export const generateMetadata = createOgImageMetadataGenerator({\n * ogImageBaseUrl: '/api/og',\n * siteUrl: settings.app.siteUrl,\n * defaultParams: {\n * siteName: settings.app.name,\n * logo: settings.app.icons.logoVector,\n * },\n * });\n *\n * // In page.tsx\n * import { generateMetadata } from '@/lib/metadata';\n *\n * export const metadata = generateMetadata({\n * title: 'My Page',\n * description: 'Description',\n * });\n * ```\n */\nexport function createOgImageMetadataGenerator(\n options: OgImageMetadataOptions\n) {\n return (\n metadata: Metadata,\n ogImageParams?: Partial<OgImageUrlParams>\n ): Metadata => {\n return generateOgImageMetadata(metadata, ogImageParams, options);\n };\n}\n\n"],"mappings":"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","names":["result"]}
1
+ {"version":3,"sources":["../../../src/og-image/utils/fonts.ts","../../../src/og-image/utils/url.ts","../../../src/og-image/utils/metadata.ts"],"sourcesContent":["/**\n * Font Utilities for OG Image Generation\n *\n * Provides dynamic font loading from Google Fonts without requiring files in public/\n * Based on Vercel's official @vercel/og documentation\n */\n\nexport interface FontConfig {\n name: string;\n weight?: 400 | 500 | 600 | 700 | 800 | 900;\n style?: 'normal' | 'italic';\n data: ArrayBuffer;\n}\n\n/**\n * Load a Google Font dynamically\n *\n * @param font - Font family name (e.g., \"Inter\", \"Roboto\", \"Manrope\")\n * @param text - Text to optimize font for (optional, reduces file size)\n * @param weight - Font weight (default: 700)\n * @returns ArrayBuffer of font data\n *\n * @example\n * const fontData = await loadGoogleFont('Manrope', 'Hello World', 700);\n */\nexport async function loadGoogleFont(\n font: string,\n text?: string,\n weight: number = 700\n): Promise<ArrayBuffer> {\n // Construct Google Fonts API URL\n let url = `https://fonts.googleapis.com/css2?family=${font}:wght@${weight}`;\n\n // Add text parameter to optimize font subset (reduces size)\n if (text) {\n url += `&text=${encodeURIComponent(text)}`;\n }\n\n try {\n // Fetch CSS containing font URL\n const css = await fetch(url, {\n headers: {\n // Required to get TTF format instead of WOFF2\n 'User-Agent':\n 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1',\n },\n }).then((res) => res.text());\n\n // Extract font URL from CSS\n const resource = css.match(/src: url\\((.+)\\) format\\('(opentype|truetype)'\\)/);\n\n if (!resource || !resource[1]) {\n throw new Error(`Failed to parse font URL from CSS for font: ${font}`);\n }\n\n // Fetch actual font file\n const response = await fetch(resource[1]);\n\n if (response.status !== 200) {\n throw new Error(`Failed to fetch font data: HTTP ${response.status}`);\n }\n\n return await response.arrayBuffer();\n } catch (error) {\n console.error(`Error loading Google Font \"${font}\":`, error);\n throw new Error(`Failed to load font \"${font}\": ${error instanceof Error ? error.message : 'Unknown error'}`);\n }\n}\n\n/**\n * Load multiple Google Fonts\n *\n * @param fonts - Array of font configurations to load\n * @returns Array of FontConfig objects ready for ImageResponse\n *\n * @example\n * const fonts = await loadGoogleFonts([\n * { family: 'Manrope', weight: 700 },\n * { family: 'Inter', weight: 400 }\n * ]);\n */\nexport async function loadGoogleFonts(\n fonts: Array<{\n family: string;\n weight?: 400 | 500 | 600 | 700 | 800 | 900;\n style?: 'normal' | 'italic';\n text?: string;\n }>\n): Promise<FontConfig[]> {\n const fontConfigs = await Promise.all(\n fonts.map(async ({ family, weight = 700, style = 'normal', text }) => {\n const data = await loadGoogleFont(family, text, weight);\n return {\n name: family,\n weight,\n style,\n data,\n };\n })\n );\n\n return fontConfigs;\n}\n\n/**\n * Create a font loader with caching\n *\n * Useful for reusing font data across multiple OG image requests\n *\n * @example\n * const fontLoader = createFontLoader();\n * const font = await fontLoader.load('Manrope', 700);\n */\nexport function createFontLoader() {\n const cache = new Map<string, Promise<ArrayBuffer>>();\n\n return {\n /**\n * Load a font with caching\n */\n async load(\n family: string,\n weight: number = 700,\n text?: string\n ): Promise<ArrayBuffer> {\n const cacheKey = `${family}-${weight}-${text || 'all'}`;\n\n if (!cache.has(cacheKey)) {\n cache.set(cacheKey, loadGoogleFont(family, text, weight));\n }\n\n return cache.get(cacheKey)!;\n },\n\n /**\n * Clear the cache\n */\n clear() {\n cache.clear();\n },\n\n /**\n * Get cache size\n */\n size() {\n return cache.size;\n },\n };\n}\n\n","/**\n * URL Generation Helpers for OG Images\n *\n * Utilities to generate OG image URLs with proper query parameters\n */\n\n/** Default OG Image API base URL */\nconst DEFAULT_OG_IMAGE_BASE_URL = 'https://djangocfg.com/api/og';\n\n/**\n * Encode string to base64 with Unicode support\n * Works in both browser and Node.js environments\n */\nfunction encodeBase64(str: string): string {\n // Node.js environment\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(str, 'utf-8').toString('base64');\n }\n // Browser environment - handle Unicode via UTF-8 encoding\n return btoa(unescape(encodeURIComponent(str)));\n}\n\n/**\n * Decode base64 string with Unicode support\n * Works in both browser, Node.js, and Edge Runtime environments\n */\nfunction decodeBase64(str: string): string {\n // Node.js environment\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(str, 'base64').toString('utf-8');\n }\n // Edge Runtime / Browser environment - handle Unicode via UTF-8 decoding\n // atob is available in Edge Runtime\n try {\n const binaryString = atob(str);\n // Convert binary string to UTF-8\n return decodeURIComponent(\n binaryString\n .split('')\n .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join('')\n );\n } catch (error) {\n // Fallback to simpler method if above fails\n return decodeURIComponent(escape(atob(str)));\n }\n}\n\n/**\n * OG Image URL parameters\n * All parameters can be encoded in URL via base64\n */\nexport interface OgImageUrlParams {\n /** Page title */\n title: string;\n /** Page description (optional) */\n description?: string;\n /** Site name (optional) */\n siteName?: string;\n /** Logo URL (optional) */\n logo?: string;\n /** Background type: 'gradient' or 'solid' */\n backgroundType?: 'gradient' | 'solid';\n /** Gradient start color (hex) */\n gradientStart?: string;\n /** Gradient end color (hex) */\n gradientEnd?: string;\n /** Background color (for solid type) */\n backgroundColor?: string;\n /** Title font size (px) */\n titleSize?: number;\n /** Title font weight */\n titleWeight?: number;\n /** Title text color */\n titleColor?: string;\n /** Description font size (px) */\n descriptionSize?: number;\n /** Description text color */\n descriptionColor?: string;\n /** Site name font size (px) */\n siteNameSize?: number;\n /** Site name text color */\n siteNameColor?: string;\n /** Padding (px) */\n padding?: number;\n /** Logo size (px) */\n logoSize?: number;\n /** Show logo flag */\n showLogo?: boolean;\n /** Show site name flag */\n showSiteName?: boolean;\n /** Additional custom parameters */\n [key: string]: string | number | boolean | undefined;\n}\n\n/**\n * Options for generating OG image URL\n */\nexport interface GenerateOgImageUrlOptions {\n /**\n * Base URL of the OG image API route\n * @default 'https://djangocfg.com/api/og'\n */\n baseUrl?: string;\n /**\n * If true, encode params as base64 for safer URLs\n * @default true\n */\n useBase64?: boolean;\n}\n\n/**\n * Generate OG image URL with query parameters or base64 encoding\n *\n * @param params - URL parameters for the OG image\n * @param options - Generation options (baseUrl, useBase64)\n * @returns Complete OG image URL with encoded parameters\n *\n * @example\n * ```typescript\n * // Using default baseUrl (https://djangocfg.com/api/og)\n * const url = generateOgImageUrl({\n * title: 'My Page Title',\n * description: 'Page description here',\n * });\n *\n * // With custom baseUrl\n * const url = generateOgImageUrl(\n * { title: 'My Page' },\n * { baseUrl: '/api/og' }\n * );\n * ```\n */\nexport function generateOgImageUrl(\n params: OgImageUrlParams,\n options: GenerateOgImageUrlOptions = {}\n): string {\n const {\n baseUrl = DEFAULT_OG_IMAGE_BASE_URL,\n useBase64 = true\n } = options;\n\n if (useBase64) {\n // Clean params - remove undefined/null/empty values\n const cleanParams: Record<string, string | number | boolean> = {};\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null && value !== '') {\n cleanParams[key] = value;\n }\n });\n\n // Encode as base64 (Unicode-safe)\n const jsonString = JSON.stringify(cleanParams);\n const base64Data = encodeBase64(jsonString);\n\n // CRITICAL: Use path parameter instead of query parameter\n // Next.js strips query params in internal requests for metadata generation\n // Using /api/og/[data] instead of /api/og?data=... preserves the data\n // IMPORTANT: Add trailing slash to avoid 308 redirects which cause timeouts in crawlers\n return `${baseUrl}/${base64Data}/`;\n } else {\n // Legacy query params mode\n const searchParams = new URLSearchParams();\n\n // Add all defined parameters\n Object.entries(params).forEach(([key, value]) => {\n if (value !== undefined && value !== null && value !== '') {\n searchParams.append(key, String(value));\n }\n });\n\n const query = searchParams.toString();\n return query ? `${baseUrl}?${query}` : baseUrl;\n }\n}\n\n/**\n * Get absolute OG image URL from relative path\n *\n * Useful for generating absolute URLs required by Open Graph meta tags\n *\n * @param relativePath - Relative OG image path (e.g., '/api/og?title=Hello')\n * @param siteUrl - Base site URL (e.g., 'https://example.com')\n * @returns Absolute URL\n *\n * @example\n * ```typescript\n * const absolute = getAbsoluteOgImageUrl(\n * '/api/og?title=Hello',\n * 'https://example.com'\n * );\n * // Result: https://example.com/api/og?title=Hello\n * ```\n */\nexport function getAbsoluteOgImageUrl(\n relativePath: string,\n siteUrl: string\n): string {\n // If path is already an absolute URL, return as-is\n if (relativePath.startsWith('http://') || relativePath.startsWith('https://')) {\n return relativePath;\n }\n\n // Remove trailing slash from site URL\n const cleanSiteUrl = siteUrl.replace(/\\/$/, '');\n\n // Ensure relative path starts with /\n const cleanPath = relativePath.startsWith('/')\n ? relativePath\n : `/${relativePath}`;\n\n return `${cleanSiteUrl}${cleanPath}`;\n}\n\n/**\n * Create OG image URL builder with preset configuration\n *\n * Useful when you want to reuse the same base URL and default parameters\n *\n * @param defaults - Default parameters to merge with each URL generation\n * @param options - Default options (baseUrl, useBase64)\n * @returns URL builder function\n *\n * @example\n * ```typescript\n * const buildOgUrl = createOgImageUrlBuilder(\n * { siteName: 'My Site', logo: '/logo.png' },\n * { baseUrl: '/api/og' }\n * );\n *\n * const url1 = buildOgUrl({ title: 'Page 1' });\n * const url2 = buildOgUrl({ title: 'Page 2', description: 'Custom desc' });\n * ```\n */\nexport function createOgImageUrlBuilder(\n defaults: Partial<OgImageUrlParams> = {},\n options: GenerateOgImageUrlOptions = {}\n) {\n return (params: OgImageUrlParams): string => {\n return generateOgImageUrl(\n { ...defaults, ...params },\n options\n );\n };\n}\n\n/**\n * Parse OG image URL parameters from a URL string (legacy query params)\n *\n * @param url - Full or relative URL with query parameters\n * @returns Parsed parameters object\n *\n * @example\n * ```typescript\n * const params = parseOgImageUrl('/api/og?title=Hello&description=World');\n * // Result: { title: 'Hello', description: 'World' }\n * ```\n */\nexport function parseOgImageUrl(url: string): Record<string, string> {\n try {\n const urlObj = new URL(url, 'http://dummy.com');\n const params: Record<string, string> = {};\n\n urlObj.searchParams.forEach((value, key) => {\n params[key] = value;\n });\n\n return params;\n } catch {\n return {};\n }\n}\n\n/**\n * Parse OG image data from base64-encoded query parameter\n *\n * Use this in your API route to decode the `data` parameter\n * Supports both base64 (new) and legacy query params format\n *\n * @param searchParams - URL search params or request object\n * @returns Parsed OG image parameters\n *\n * @example\n * ```typescript\n * // In Next.js API route (pages/api/og.ts)\n * export default function handler(req) {\n * const params = parseOgImageData(req.query);\n * // { title: 'Hello', description: 'World' }\n * }\n *\n * // In Next.js App Router (app/api/og/route.ts)\n * export async function GET(request: Request) {\n * const { searchParams } = new URL(request.url);\n * const params = parseOgImageData(Object.fromEntries(searchParams));\n * // { title: 'Hello', description: 'World' }\n * }\n * ```\n */\nexport function parseOgImageData(\n searchParams: Record<string, string | string[] | undefined> | URLSearchParams\n): Record<string, string> {\n try {\n // Handle URLSearchParams\n let params: Record<string, string | undefined>;\n\n if (searchParams instanceof URLSearchParams) {\n // Convert URLSearchParams to object\n params = {};\n for (const [key, value] of searchParams.entries()) {\n params[key] = value;\n }\n } else {\n params = searchParams as Record<string, string | undefined>;\n }\n\n // Debug logging\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Input params keys:', Object.keys(params));\n console.log('[parseOgImageData] Input params:', params);\n }\n\n // Check for base64-encoded data parameter\n const dataParam = params.data;\n if (dataParam && typeof dataParam === 'string' && dataParam.trim() !== '') {\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Found data param, length:', dataParam.length);\n }\n\n try {\n const decoded = decodeBase64(dataParam);\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Decoded string:', decoded.substring(0, 100));\n }\n\n const parsed = JSON.parse(decoded);\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Parsed JSON:', parsed);\n }\n\n // Ensure all values are strings\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(parsed)) {\n if (value !== undefined && value !== null) {\n result[key] = String(value);\n }\n }\n\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Result:', result);\n }\n\n return result;\n } catch (decodeError) {\n console.error('[parseOgImageData] Error decoding/parsing data param:', decodeError);\n if (decodeError instanceof Error) {\n console.error('[parseOgImageData] Error message:', decodeError.message);\n }\n // Fall through to legacy query params\n }\n } else {\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] No data param found or empty');\n }\n }\n\n // Fallback to legacy query params format\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(params)) {\n if (key !== 'data' && value !== undefined && value !== null) {\n result[key] = Array.isArray(value) ? value[0] : String(value);\n }\n }\n\n if (process.env.NODE_ENV === 'development') {\n console.log('[parseOgImageData] Fallback result:', result);\n }\n\n return result;\n } catch (error) {\n console.error('[parseOgImageData] Unexpected error:', error);\n return {};\n }\n}\n\n// Export base64 utilities for advanced use cases\nexport { encodeBase64, decodeBase64 };\n","/**\n * Metadata Utilities for OG Images\n *\n * Helpers to automatically add og:image to Next.js metadata\n */\n\nimport type { Metadata } from 'next';\nimport { generateOgImageUrl, getAbsoluteOgImageUrl, OgImageUrlParams} from './url';\n\n/**\n * Options for generating OG image metadata\n */\nexport interface OgImageMetadataOptions {\n /** Base URL of the OG image API route (e.g., '/api/og') */\n ogImageBaseUrl?: string;\n /** Site URL for absolute URLs (e.g., 'https://example.com') */\n siteUrl?: string;\n /** Default parameters to merge with page-specific params */\n defaultParams?: Partial<OgImageUrlParams>;\n /** Whether to use base64 encoding (default: true) */\n useBase64?: boolean;\n}\n\n/**\n * Extract title from metadata\n */\nfunction extractTitle(metadata: Metadata): string {\n if (typeof metadata.title === 'string') {\n return metadata.title;\n }\n if (metadata.title) {\n if ('default' in metadata.title) {\n return metadata.title.default;\n }\n if ('absolute' in metadata.title) {\n return metadata.title.absolute;\n }\n }\n return '';\n}\n\n/**\n * Extract description from metadata\n */\nfunction extractDescription(metadata: Metadata): string {\n if (typeof metadata.description === 'string') {\n return metadata.description;\n }\n return '';\n}\n\n/**\n * Generate Next.js metadata with OG image\n *\n * Automatically adds og:image, twitter:image, and other OG meta tags\n * Automatically extracts title and description from metadata if not provided\n *\n * @param metadata - Base metadata object\n * @param ogImageParams - Optional parameters for OG image generation (if not provided, extracted from metadata)\n * @param options - Configuration options\n * @returns Enhanced metadata with OG image\n *\n * @example\n * ```typescript\n * // In page.tsx or layout.tsx\n * import { generateOgImageMetadata } from '@djangocfg/nextjs/og-image';\n * import { settings } from '@/core/settings';\n *\n * export const metadata = generateOgImageMetadata(\n * {\n * title: 'My Page',\n * description: 'Page description',\n * },\n * undefined, // Will auto-extract from metadata\n * {\n * ogImageBaseUrl: '/api/og',\n * siteUrl: settings.app.siteUrl,\n * defaultParams: {\n * siteName: settings.app.name,\n * logo: settings.app.icons.logoVector,\n * },\n * }\n * );\n * ```\n */\n/**\n * Get site URL automatically from environment\n * Priority: NEXT_PUBLIC_SITE_URL > VERCEL_URL > fallback\n */\nfunction getSiteUrl(): string {\n // Try NEXT_PUBLIC_SITE_URL first (most reliable)\n if (typeof process !== 'undefined' && process.env.NEXT_PUBLIC_SITE_URL) {\n return process.env.NEXT_PUBLIC_SITE_URL;\n }\n \n // Development fallback\n return '';\n}\n\nexport function generateOgImageMetadata(\n metadata: Metadata,\n ogImageParams?: Partial<OgImageUrlParams>,\n options: OgImageMetadataOptions = {}\n): Metadata {\n const {\n ogImageBaseUrl = 'https://djangocfg.com/api/og',\n siteUrl: providedSiteUrl,\n defaultParams = {},\n useBase64 = true,\n } = options;\n\n // Automatically determine siteUrl if not provided or is undefined\n const siteUrl = providedSiteUrl && providedSiteUrl !== 'undefined' \n ? providedSiteUrl \n : getSiteUrl();\n\n // Auto-extract title and description from metadata if not provided\n const extractedTitle = extractTitle(metadata);\n const extractedDescription = extractDescription(metadata);\n\n // Merge with provided params (provided params take precedence)\n const finalOgImageParams: OgImageUrlParams = {\n ...defaultParams,\n title: ogImageParams?.title || extractedTitle || defaultParams.title || '',\n description: ogImageParams?.description || extractedDescription || defaultParams.description || '',\n ...ogImageParams,\n };\n\n // Get alt text for image (title or siteName as fallback)\n const imageAlt = finalOgImageParams.title || finalOgImageParams.siteName;\n\n // Generate relative OG image URL\n const relativeOgImageUrl = generateOgImageUrl(\n finalOgImageParams,\n { baseUrl: ogImageBaseUrl, useBase64 }\n );\n\n // CRITICAL: Use absolute URL to ensure query params are preserved\n // Next.js might strip query params from relative URLs in some cases\n // Absolute URLs ensure the full URL with params is used\n const ogImageUrl = siteUrl\n ? getAbsoluteOgImageUrl(relativeOgImageUrl, siteUrl)\n : relativeOgImageUrl;\n\n // Normalize existing images to arrays\n const existingOgImages = metadata.openGraph?.images\n ? Array.isArray(metadata.openGraph.images)\n ? metadata.openGraph.images\n : [metadata.openGraph.images]\n : [];\n\n const existingTwitterImages = metadata.twitter?.images\n ? Array.isArray(metadata.twitter.images)\n ? metadata.twitter.images\n : [metadata.twitter.images]\n : [];\n\n // Build final metadata object\n const finalMetadata: Metadata = {\n ...metadata,\n openGraph: {\n ...metadata.openGraph,\n images: [\n ...existingOgImages,\n {\n url: ogImageUrl,\n width: 1200,\n height: 630,\n alt: imageAlt,\n },\n ],\n },\n twitter: {\n ...metadata.twitter,\n card: 'summary_large_image',\n images: [\n ...existingTwitterImages,\n {\n url: ogImageUrl,\n alt: imageAlt,\n },\n ],\n },\n };\n\n // Automatically add metadataBase if siteUrl is an absolute URL\n // metadataBase requires absolute URL - skip if siteUrl is relative path (like /cfg/admin)\n // Only add if not already set in input metadata\n if (!finalMetadata.metadataBase && siteUrl) {\n // Check if siteUrl is an absolute URL (starts with http:// or https://)\n if (siteUrl.startsWith('http://') || siteUrl.startsWith('https://')) {\n try {\n finalMetadata.metadataBase = new URL(siteUrl);\n } catch (e) {\n // If URL construction fails, skip metadataBase\n // This shouldn't happen if we check for http/https, but just in case\n }\n }\n }\n\n return finalMetadata;\n}\n\n/**\n * Create OG image metadata generator with preset configuration\n *\n * Useful when you want to reuse the same configuration across multiple pages\n *\n * @param options - Configuration options\n * @returns Metadata generator function\n *\n * @example\n * ```typescript\n * // In a shared file (e.g., lib/metadata.ts)\n * import { createOgImageMetadataGenerator } from '@djangocfg/nextjs/og-image';\n * import { settings } from '@/core/settings';\n *\n * export const generateMetadata = createOgImageMetadataGenerator({\n * ogImageBaseUrl: '/api/og',\n * siteUrl: settings.app.siteUrl,\n * defaultParams: {\n * siteName: settings.app.name,\n * logo: settings.app.icons.logoVector,\n * },\n * });\n *\n * // In page.tsx\n * import { generateMetadata } from '@/lib/metadata';\n *\n * export const metadata = generateMetadata({\n * title: 'My Page',\n * description: 'Description',\n * });\n * ```\n */\nexport function createOgImageMetadataGenerator(\n options: OgImageMetadataOptions\n) {\n return (\n metadata: Metadata,\n ogImageParams?: Partial<OgImageUrlParams>\n ): Metadata => {\n return generateOgImageMetadata(metadata, ogImageParams, options);\n };\n}\n\n"],"mappings":"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","names":["result"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/nextjs",
3
- "version": "2.1.51",
3
+ "version": "2.1.53",
4
4
  "description": "Next.js server utilities: sitemap, health, OG images, contact forms, navigation, config",
5
5
  "keywords": [
6
6
  "nextjs",
@@ -133,9 +133,9 @@
133
133
  "web-push": "^3.6.7"
134
134
  },
135
135
  "devDependencies": {
136
- "@djangocfg/imgai": "^2.1.51",
137
- "@djangocfg/layouts": "^2.1.51",
138
- "@djangocfg/typescript-config": "^2.1.51",
136
+ "@djangocfg/imgai": "^2.1.53",
137
+ "@djangocfg/layouts": "^2.1.53",
138
+ "@djangocfg/typescript-config": "^2.1.53",
139
139
  "@types/node": "^24.7.2",
140
140
  "@types/react": "19.2.2",
141
141
  "@types/react-dom": "19.2.1",
@@ -156,7 +156,8 @@ export function generateOgImageUrl(
156
156
  // CRITICAL: Use path parameter instead of query parameter
157
157
  // Next.js strips query params in internal requests for metadata generation
158
158
  // Using /api/og/[data] instead of /api/og?data=... preserves the data
159
- return `${baseUrl}/${base64Data}`;
159
+ // IMPORTANT: Add trailing slash to avoid 308 redirects which cause timeouts in crawlers
160
+ return `${baseUrl}/${base64Data}/`;
160
161
  } else {
161
162
  // Legacy query params mode
162
163
  const searchParams = new URLSearchParams();