tpmjs/docs/prd-dynamic-og-images.md
2025-12-17 11:06:57 +10:00

16 KiB
Raw Blame History

PRD: AI-Generated OG Images for TPMJS

Overview

Implement a system where every page on tpmjs.com (both static and dynamic) has a unique, AI-generated Open Graph image created by Gemini. The OG images should be contextually relevant, visually distinctive, and reflect the actual content of each page.

Goals

  1. Unique visual identity - Every page gets a custom OG image, not generic templates
  2. Content-aware generation - Images reflect the actual page content (tool descriptions, categories, etc.)
  3. Performance-optimized - Pre-generate for static pages, on-demand with caching for dynamic
  4. Cost-efficient - Minimize redundant API calls through intelligent caching

Technical Architecture

Page Types & Generation Strategy

Page Type Example Generation Strategy
Static /, /about, /playground Build-time generation, stored in CDN
Semi-static /tools, /categories Build-time with ISR revalidation
Dynamic /tool/[slug] On-demand with aggressive caching
User-generated Future: /user/[id] On-demand with TTL cache

System Components

┌─────────────────────────────────────────────────────────────────┐
│                        Request Flow                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  Browser/Crawler                                                 │
│       │                                                          │
│       ▼                                                          │
│  ┌─────────────┐    ┌──────────────┐    ┌─────────────────────┐ │
│  │ Next.js     │───▶│ OG Image     │───▶│ Vercel KV/Redis     │ │
│  │ Metadata    │    │ Route        │    │ Cache Layer         │ │
│  │ Generator   │    │ /api/og/[..] │    │                     │ │
│  └─────────────┘    └──────────────┘    └─────────────────────┘ │
│                            │                      │              │
│                            │ cache miss           │ cache hit    │
│                            ▼                      ▼              │
│                     ┌──────────────┐       Return cached         │
│                     │ Content      │       image URL             │
│                     │ Extractor    │                             │
│                     └──────────────┘                             │
│                            │                                     │
│                            ▼                                     │
│                     ┌──────────────┐                             │
│                     │ Prompt       │                             │
│                     │ Builder      │                             │
│                     └──────────────┘                             │
│                            │                                     │
│                            ▼                                     │
│                     ┌──────────────┐                             │
│                     │ Gemini API   │                             │
│                     │ (Imagen 3)   │                             │
│                     └──────────────┘                             │
│                            │                                     │
│                            ▼                                     │
│                     ┌──────────────┐                             │
│                     │ Vercel Blob  │                             │
│                     │ Storage      │                             │
│                     └──────────────┘                             │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Implementation Details

1. Content Extraction Service

Each page type needs a content extractor that produces structured data for the prompt:

// packages/og-generator/src/extractors/types.ts
interface PageContent {
  pageType: 'home' | 'tool' | 'category' | 'search' | 'playground' | 'about';
  title: string;
  description: string;
  keywords: string[];
  primaryColor?: string;

  // Page-specific data
  toolData?: {
    name: string;
    category: string;
    tier: 'minimal' | 'rich';
    capabilities: string[];
    npmDownloads: number;
  };

  categoryData?: {
    name: string;
    toolCount: number;
    topTools: string[];
  };
}

2. Prompt Engineering

The prompt should be carefully crafted to produce consistent, on-brand images:

// packages/og-generator/src/prompt-builder.ts

function buildPrompt(content: PageContent): string {
  const baseStyle = `
    Create a 1200x630 pixel Open Graph image with these constraints:
    - Modern, clean tech aesthetic
    - Dark background (#0a0a0a to #1a1a1a gradient)
    - Accent colors: cyan (#00d4ff), purple (#8b5cf6)
    - Abstract geometric shapes or flowing lines
    - NO text in the image (text added via overlay)
    - Professional, minimalist design
    - Subtle depth and dimensionality
  `;

  const pageContext = buildPageContext(content);

  return `${baseStyle}\n\nContext for this specific page:\n${pageContext}`;
}

function buildPageContext(content: PageContent): string {
  switch (content.pageType) {
    case 'tool':
      return `
        This image is for a developer tool called "${content.toolData.name}".
        Category: ${content.toolData.category}
        Capabilities: ${content.toolData.capabilities.join(', ')}
        Visual motif: Represent the concept of "${content.toolData.category}"
        abstractly using shapes, gradients, or symbolic elements.
        Mood: Professional, powerful, trustworthy.
      `;

    case 'category':
      return `
        This image represents a category of developer tools: "${content.categoryData.name}".
        Contains ${content.categoryData.toolCount} tools.
        Visual motif: Abstract representation of "${content.categoryData.name}"
        as a concept - use symbolic shapes and patterns.
        Mood: Organized, comprehensive, discoverable.
      `;

    case 'home':
      return `
        This is the homepage for TPMJS - a registry of AI/LLM tools for developers.
        Visual motif: Interconnected nodes, flowing data streams, AI neural patterns.
        Mood: Cutting-edge, innovative, developer-focused.
      `;

    // ... other page types
  }
}

3. API Route Implementation

// apps/web/src/app/api/og/[...path]/route.ts

import { ImageResponse } from 'next/og';
import { kv } from '@vercel/kv';
import { put } from '@vercel/blob';

export const runtime = 'edge';
export const maxDuration = 60;

export async function GET(
  request: Request,
  { params }: { params: { path: string[] } }
) {
  const pagePath = '/' + params.path.join('/');
  const cacheKey = `og:${pagePath}`;

  // 1. Check cache
  const cached = await kv.get<string>(cacheKey);
  if (cached) {
    return Response.redirect(cached);
  }

  // 2. Extract page content
  const content = await extractPageContent(pagePath);

  // 3. Check if we have a pre-generated image (build-time)
  const preGenerated = await checkPreGeneratedImage(pagePath);
  if (preGenerated) {
    await kv.set(cacheKey, preGenerated, { ex: 86400 }); // 24hr cache
    return Response.redirect(preGenerated);
  }

  // 4. Generate new image via Gemini
  const prompt = buildPrompt(content);
  const imageBuffer = await generateWithGemini(prompt);

  // 5. Upload to Vercel Blob
  const { url } = await put(`og/${pagePath}.png`, imageBuffer, {
    access: 'public',
    contentType: 'image/png',
  });

  // 6. Cache the URL
  await kv.set(cacheKey, url, { ex: 86400 });

  // 7. Return redirect to blob URL
  return Response.redirect(url);
}

4. Metadata Integration

// apps/web/src/app/tool/[slug]/page.tsx

export async function generateMetadata({ params }): Promise<Metadata> {
  const tool = await getTool(params.slug);

  return {
    title: tool.name,
    description: tool.description,
    openGraph: {
      title: tool.name,
      description: tool.description,
      images: [{
        url: `https://tpmjs.com/api/og/tool/${params.slug}`,
        width: 1200,
        height: 630,
        alt: `${tool.name} - TPMJS Tool`,
      }],
    },
    twitter: {
      card: 'summary_large_image',
      images: [`https://tpmjs.com/api/og/tool/${params.slug}`],
    },
  };
}

5. Build-Time Pre-Generation

// scripts/generate-og-images.ts

async function preGenerateOGImages() {
  // Static pages - always regenerate on build
  const staticPages = ['/', '/about', '/playground', '/tools'];

  // Semi-static - regenerate if content changed
  const categories = await prisma.category.findMany();
  const categoryPages = categories.map(c => `/category/${c.slug}`);

  // High-traffic tools - pre-generate top 100
  const topTools = await prisma.tool.findMany({
    orderBy: { qualityScore: 'desc' },
    take: 100,
  });
  const toolPages = topTools.map(t => `/tool/${t.slug}`);

  const allPages = [...staticPages, ...categoryPages, ...toolPages];

  for (const page of allPages) {
    await generateAndStoreOGImage(page);
    await sleep(1000); // Rate limiting
  }
}

Caching Strategy

Cache Layers

  1. Vercel Edge Cache - CDN caching of the redirect response
  2. Vercel KV - URL mapping cache (path → blob URL)
  3. Vercel Blob - Permanent image storage

Cache Invalidation

Trigger Action
Tool updated Invalidate /tool/[slug] cache key
New tool added Pre-generate OG image via webhook
Build/deploy Regenerate static page images
Manual Admin endpoint to force regeneration
// apps/web/src/app/api/admin/invalidate-og/route.ts

export async function POST(request: Request) {
  const { path, secret } = await request.json();

  if (secret !== process.env.ADMIN_SECRET) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 });
  }

  await kv.del(`og:${path}`);

  // Optionally trigger regeneration
  await fetch(`https://tpmjs.com/api/og${path}`);

  return Response.json({ success: true });
}

Cost Considerations

Gemini API Costs

  • Imagen 3 pricing: ~$0.02-0.04 per image (estimate)
  • Initial generation: ~500 pages × $0.03 = $15
  • Monthly regeneration: ~100 new tools × $0.03 = $3

Storage Costs

  • Vercel Blob: $0.15/GB/month
  • Estimated: 500 images × 500KB = 250MB = ~$0.04/month

Optimization Strategies

  1. Aggressive caching - 24-48 hour TTL minimum
  2. Batch generation - Build-time for predictable pages
  3. Lazy generation - On-demand for long-tail pages
  4. Image optimization - Compress before storage

Fallback Strategy

If Gemini API fails or rate limits:

  1. Primary: Return cached image if available (even if stale)
  2. Secondary: Return pre-designed template image with text overlay
  3. Tertiary: Return generic TPMJS branded fallback image
async function generateOGImage(pagePath: string): Promise<string> {
  try {
    return await generateWithGemini(pagePath);
  } catch (error) {
    console.error('Gemini generation failed:', error);

    // Fallback to template
    return await generateTemplateImage(pagePath);
  }
}

Text Overlay System

Since AI-generated images shouldn't contain text (unreliable), add text via a compositing layer:

// Using @vercel/og or sharp for text overlay

async function compositeOGImage(
  baseImageUrl: string,
  content: PageContent
): Promise<Buffer> {
  const baseImage = await fetch(baseImageUrl).then(r => r.arrayBuffer());

  // Use sharp or canvas to overlay:
  // - Page title (top-left or centered)
  // - TPMJS logo (bottom-right)
  // - Optional: category badge, stats, etc.

  return await sharp(baseImage)
    .composite([
      { input: titleBuffer, top: 50, left: 50 },
      { input: logoBuffer, top: 530, left: 1050 },
    ])
    .png()
    .toBuffer();
}

Implementation Phases

Phase 1: Foundation (Week 1)

  • Set up Vercel Blob storage
  • Set up Vercel KV for caching
  • Create basic /api/og/[...path] route
  • Implement content extractors for each page type

Phase 2: Gemini Integration (Week 2)

  • Integrate Gemini Imagen 3 API
  • Design and test prompts for each page type
  • Implement text overlay compositing
  • Add fallback image generation

Phase 3: Optimization (Week 3)

  • Build-time pre-generation script
  • Cache invalidation webhooks
  • Monitoring and alerting
  • Cost tracking dashboard

Phase 4: Polish (Week 4)

  • A/B test different prompt styles
  • Refine prompts based on results
  • Admin UI for manual regeneration
  • Documentation

Success Metrics

  1. Coverage: 100% of pages have unique OG images
  2. Performance: OG image response < 500ms (cache hit)
  3. Quality: Manual review score > 4/5 on sample of images
  4. Cost: < $50/month total (API + storage)

Open Questions

  1. Model choice: Gemini Imagen 3 vs DALL-E 3 vs Stable Diffusion?
  2. Text rendering: Overlay vs bake into prompt?
  3. Style consistency: How to maintain brand while varying per-page?
  4. A/B testing: How to measure OG image effectiveness?

Appendix: Example Prompts

Homepage

Create a 1200x630 Open Graph image. Dark gradient background (#0a0a0a to #1a1a1a).
Abstract visualization of an AI neural network with glowing cyan (#00d4ff) and
purple (#8b5cf6) nodes connected by flowing data streams. Minimalist, professional,
futuristic. No text. Subtle grid pattern overlay. Represents a developer tool registry
for AI and LLM integrations.

Tool Page (e.g., file-search tool)

Create a 1200x630 Open Graph image. Dark gradient background. Abstract representation
of file searching - interconnected folder icons, magnifying glass motif, flowing
document shapes. Color scheme: cyan and purple accents on dark. Minimalist style,
no text. Professional developer tool aesthetic. Represents a powerful file search
capability for AI assistants.

Category Page (e.g., "Database" category)

Create a 1200x630 Open Graph image. Dark gradient background. Abstract database
visualization - cylindrical shapes, connected nodes, data flow patterns. Organized
grid structure suggesting multiple tools. Cyan and purple accent colors. Minimalist,
no text. Professional aesthetic representing a collection of database tools for
AI development.

Note: "Gemini 3 nanobanana" interpreted as Google Gemini with Imagen 3 for image generation. Actual model availability and pricing should be verified.