Skip to content

mehedimi/og-server

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OG Server

A high-performance Open Graph (OG) image server built in Rust, powering dynamic social card generation for UmamiQR — a free QR code digital menu system for restaurants.

Generates 1200×630px WebP images on the fly via query parameters, with in-memory caching and an embedded font — zero runtime file I/O.

Features

  • Dynamic OG image generation from URL query parameters
  • In-memory LRU cache (Moka) — up to 500 images with 24h TTL
  • CPU-bound rendering offloaded to Tokio's blocking thread pool
  • Embedded font (Fraunces) — compiled into the binary, no file system dependency
  • Branded design matching UmamiQR's color palette
  • Gzip compression on responses
  • Health check endpoint for monitoring

Quick Start

Prerequisites

  • Rust 2021 edition or later (rustc 1.75+)
  • cargo

Build & Run

# Development
cargo run

# Production-optimized release
cargo run --release

The server starts on 127.0.0.1:3100 by default. Override the port:

PORT=8080 cargo run --release

API

GET /og

Generates an Open Graph image as a PNG.

Parameter Type Required Description
title string No Main headline (max 2 lines). Default: "The Title"
description string No Subtitle/description (max 2 lines). Default: "Free QR Digital Menu for Restaurants"
section string No Optional golden badge label. Omit to hide

Response:

  • Content-Type: image/webp
  • Cache-Control: public, max-age=86400
  • Content-Encoding: gzip (if client accepts compression)

Example

# Basic
curl "http://127.0.0.1:3100/og?title=Hello%20World&description=My%20awesome%20project" -o og.webp

# With section badge
curl "http://127.0.0.1:3100/og?title=Digital%20Restaurant%20Menu&description=Free%20QR%20code%20menu%20system&section=About" -o og.webp

# Save and set as meta tag
<meta property="og:image" content="https://your-domain.com/og?title=Your%20Title&description=Your%20Description" />

GET /health

Returns 200 OK with body "ok".

Architecture

┌──────────┐   Query params    ┌───────────┐   spawn_blocking    ┌──────────┐
│  Client   │ ──────────────►  │   Axum    │ ──────────────────► │  Takumi  │
│  (Browser)│ ◄────────────── │  Server    │ ◄────────────────── │ (Render) │
└──────────┘    WebP + gzip   └─────┬─────┘                     └──────────┘
                                    │
                           ┌────────▼────────┐
                           │  Moka Cache     │
                           │  (500 entries)  │
                           └─────────────────┘

Stack

Component Tech
Web framework Axum 0.8
Async runtime Tokio
OG renderer Takumi 1.0
Cache Moka
Font Fraunces (embedded at compile time)
Output 1200×630 WebP (Open Graph standard)

Deployment

Docker

FROM rust:1-slim AS builder
WORKDIR /app
COPY . .
RUN cargo build --release

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y libssl3 ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /app/target/release/og-server /usr/local/bin/
EXPOSE 3100
CMD ["og-server"]

Reverse Proxy (nginx)

Place behind a reverse proxy that terminates TLS:

server {
    listen 443 ssl;
    server_name your-domain.com;

    location /og {
        proxy_pass http://127.0.0.1:3100;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

AI Agent Integration

When a URL from your site is shared in AI tools (ChatGPT, Gemini, Claude, Perplexity, Google AI Overviews), the AI fetches the page and reads <meta> tags to generate rich previews with context-aware answers.

How It Works

User shares link          AI fetches page           AI reads meta tags
     │                       │                           │
     ▼                       ▼                           ▼
 "Check this out!"  ──►  GET /page  ──►  og:title  ──►  "This page is about: ..."
                         response     og:image          Shows thumbnail
                                      og:description

Required Meta Tags

Add these to your HTML <head>. The values should be server-rendered per page:

<head>
  <!-- Core OG tags -->
  <meta property="og:title" content="{{ page_title }}" />
  <meta property="og:description" content="{{ page_description }}" />
  <meta property="og:image" content="https://your-domain.com/og?title={{ url_encoded_title }}&description={{ url_encoded_description }}" />
  <meta property="og:image:width" content="1200" />
  <meta property="og:image:height" content="630" />
  <meta property="og:image:type" content="image/webp" />
  <meta property="og:type" content="website" />
  <meta property="og:url" content="{{ canonical_url }}" />

  <!-- Twitter Card (same image, different format) -->
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content="{{ page_title }}" />
  <meta name="twitter:description" content="{{ page_description }}" />
  <meta name="twitter:image" content="https://your-domain.com/og?title={{ url_encoded_title }}&description={{ url_encoded_description }}" />
</head>

Agent Behavior by Platform

Platform What it reads Result
ChatGPT og:title, og:description, og:image Shows page title + thumbnail in shared link preview
Google AI Overviews Full OG + structured data Cites page content with rich context in AI answers
Claude og:title, og:description, page content References page accurately with correct title
Gemini og:image, meta description Generates visual card with branded image
Perplexity OG tags + <title> Cites source with proper attribution
Slack/Discord og:title, og:image Shows link embed with thumbnail

Example: Dynamic Page Rendering

Laravel (Blade):

<meta property="og:image"
  content="https://your-domain.com/og?title={{ urlencode($menu->name) }}&description={{ urlencode('Digital menu — ' . $menu->restaurant->name) }}&section={{ urlencode($menu->category ?? '') }}"
/>

Next.js (App Router):

export async function generateMetadata({ params }: Props) {
  const menu = await getMenu(params.id);
  const title = menu.name;
  const desc = `Digital menu — ${menu.restaurant.name}`;

  return {
    openGraph: {
      title,
      description: desc,
      images: [
        `/og?title=${encodeURIComponent(title)}&description=${encodeURIComponent(desc)}`,
      ],
    },
  };
}

Best Practices for AI Citations

  1. Unique titles — each page should have a distinct og:title so AI can differentiate content
  2. Descriptive descriptions — include context like restaurant name, cuisine type, or location
  3. Section badge — use &section= for category context (e.g., section=Appetizers, section=Drinks)
  4. Consistent branding — the OG image uses your brand colors so AI previews stay recognizable
  5. Fast response — cached responses return in <10ms, so AI crawlers never timeout

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages