We publish articles on dovito.com without touching Figma, Photoshop, or Canva. Every article gets three image assets: an animated SVG hero rendered in the browser, a static PNG for Twitter cards, and an animated GIF for rich social previews on LinkedIn, Slack, and iMessage. All three are produced from a single source of truth: the SVG markup inside the article's HTML content.

This is a full technical walkthrough of how the pipeline works, from the moment Claude generates the SVG to the moment a link preview renders on someone's phone.

The Three Image Formats and Why Each Exists

Social platforms handle link previews differently. Twitter/X strips animation from og:image tags and renders a static card. Facebook, Slack, Discord, and iMessage will render an animated GIF if the og:image points to one. LinkedIn renders the first frame of a GIF as a static image. No single format satisfies all of them.

That means every article needs at minimum:

  • SVG (inline in HTML) for the live article page, where the hero animates with CSS keyframes and native SVG <animate> elements
  • PNG (uploaded to media library) for Twitter cards and as the featuredImage fallback
  • GIF (uploaded to media library) for animated social previews via the og:image meta tag

The SVG is the source. The PNG and GIF are derived from it by capturing the browser's actual rendering of that SVG at different points in its animation timeline.

Step 1: SVG Hero Generation

Every article starts with Claude reading the design system. The MCP server exposes a get_content_design_system tool that returns the full specification: brand colors, typography rules, animation keyframes, and the SVG hero contract.

The contract is strict. Every hero must:

  • Live inside a <div class="article-hero"> container (300px height, 14px border-radius, overflow hidden)
  • Use a <svg viewBox="0 0 740 300"> with preserveAspectRatio="xMidYMid slice"
  • Start with a <rect width="740" height="300" fill="#0C0C0F"/> (jet black background)
  • Include grid dots at 10% opacity, a ghost background word at 2-2.5% opacity, and top/bottom rule lines
  • Use four animation classes: .draw for stroke-dash path reveals, .node for scale-in elements, .lbl for float-up labels, and .rpl for infinite ripple pulses

Within that structure, the creative content is unique per article. The hero at the top of this article maps the exact pipeline sequence left to right: Claude generates via MCP tools, producing an SVG hero. Puppeteer captures that hero, forking into PNG and GIF outputs. Both merge at the upload step, and finally link to the article's OG meta tags.

Why SVG instead of generated raster images? Three reasons. First, SVGs are resolution-independent and render crisp at any viewport size. Second, they support native CSS and SMIL animations without JavaScript, which means the hero animates on page load with zero runtime overhead. Third, they're text. Claude can write SVG markup the same way it writes HTML. No image generation API, no diffusion model, no external service call.

Step 2: Article Creation via MCP

The create_article MCP tool accepts a title, type, excerpt, tags, SEO fields, and an HTML content body. The content body includes the <style> block with animation keyframes, the SVG hero markup, and the full article body using the design system's component library.

The API generates a URL-safe slug from the title, sets the status to draft, assigns the authenticated user as author, and stores the raw HTML in a Postgres text column. At this point the article exists in the database but is not visible on the site. It has no images attached.

Step 3: Publishing

The publish_article tool flips the article's status from draft to published and sets the publishedAt timestamp. The SSR layer only queries for articles with status = "published", so this is the gate between draft and live.

Publishing must happen before OG image generation because the capture process needs to load the article's live URL in a real browser. Draft articles are only accessible via /blog/preview/:id behind CMS auth, and the headless browser session used for capture does not carry authentication cookies.

Step 4: The Capture Pipeline

This is where the PNG and GIF get created. The generate_og_images MCP tool orchestrates a multi-step process:

Launch Headless Chrome

Puppeteer launches a headless Chromium instance. The viewport is set to 1200x800 at 1x device scale factor, wider than the article's 740px max-width so the hero renders at its natural size.

Navigate and Validate

The browser navigates to the article's public URL and confirms a .article-hero element exists. If the article was created without an SVG hero, the tool throws an error. No fallback. No placeholder image.

Reload and Prepare

The page is reloaded to restart all CSS animations from their initial state. After reload, the script hides the fixed site navigation bar and scrolls the hero into view.

Frame Capture Loop

40 frames captured at 100ms intervals. 4 seconds of animation at 10fps. Each frame is a Puppeteer screenshot scoped to the .article-hero element, not the full page.

Static PNG Extraction

The last frame becomes the static PNG. By the 4-second mark, all entrance animations have completed. Only infinite animations (ripples, orbits, particles) are still moving.

Animated GIF Encoding

All 40 frames are fed through gif-encoder-2 using NeuQuant color quantization. The jet-black background and limited color palette compress well under GIF's LZW encoding. Typical hero GIF: 200-400 KB.

40
FRAMES CAPTURED
4s
ANIMATION DURATION
10fps
CAPTURE RATE
~300KB
TYPICAL GIF SIZE

Step 5: Upload and Linking

Both images are uploaded to the CMS media library. The tool then updates the article: featuredImage = PNG URL, ogImage = GIF URL.

The SSR template emits separate meta tags for each platform:

HTML
<!-- Facebook, Slack, Discord, iMessage -->
<meta property="og:image" content="...animated.gif" />
<meta property="og:image:type" content="image/gif" />

<!-- Twitter/X -->
<meta name="twitter:image" content="...static.png" />

The Full Workflow

Four MCP tool calls to publish an article with complete image assets:

  1. get_content_design_system to load the spec
  2. create_article with the full HTML content including the SVG hero
  3. publish_article to make it live
  4. generate_og_images to capture, upload, and link the PNG and GIF

No design tool. No image editor. No manual upload. The SVG hero animates on the live page. The GIF loops in Slack previews. The PNG renders clean on Twitter. All three come from the same source markup.

The constraint that makes it work: the SVG hero is not a nice-to-have decoration. It is a required contract that the design system enforces and the OG capture pipeline depends on. Without the hero, generate_og_images throws an error and refuses to produce images. This tight coupling means every published article ships with complete social media assets by default.