
Creating Share Images
- 12 installs
- 185 repo stars
- Updated August 4, 2026
- dicklesworthstone/meta_skill
Generates dynamic OpenGraph and Twitter share images in Next.js using next/og ImageResponse with gradients, SVG icons, and correct dimensions.
About
Shows how to build social preview cards with next/og ImageResponse, covering file naming, Satori layout rules, and design patterns. A developer uses it to add OG and Twitter share images to a Next.js app.
- opengraph-image.tsx (1200x630) and twitter-image.tsx (1200x600) conventions
- Satori rules: display:flex everywhere, inline styles, gradient text tricks
Creating Share Images by the numbers
- 12 all-time installs (skills.sh)
- Ranked #1,643 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/dicklesworthstone/meta_skill --skill creating-share-imagesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 12 |
|---|---|
| repo stars | ★ 185 |
| Last updated | August 4, 2026 |
| Repository | dicklesworthstone/meta_skill ↗ |
What it does
Generates dynamic OpenGraph and Twitter share images in Next.js using next/og ImageResponse with gradients, SVG icons, and correct dimensions.
Files
Creating Share Images for Next.js
Generate dynamic OpenGraph (1200x630) and Twitter (1200x600) images using next/og ImageResponse.
Quick Start
Create app/opengraph-image.tsx:
import { ImageResponse } from "next/og";
export const runtime = "edge";
export const alt = "Page Title - Site Description";
export const size = { width: 1200, height: 630 };
export const contentType = "image/png";
export default async function Image() {
return new ImageResponse(
(
<div style={{
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
background: "linear-gradient(145deg, #0a0a12 0%, #0f1218 50%, #0a0a12 100%)",
fontFamily: "system-ui, -apple-system, sans-serif",
}}>
<h1 style={{ fontSize: 64, color: "#ffffff", margin: 0 }}>
Page Title
</h1>
</div>
),
{ ...size }
);
}File Naming Convention
| File | Purpose | Dimensions |
|---|---|---|
opengraph-image.tsx | Facebook, LinkedIn, iMessage | 1200×630 |
twitter-image.tsx | Twitter/X cards | 1200×600 |
Place in route directory (e.g., app/about/opengraph-image.tsx for /about).
Design Patterns
Gradient Backgrounds
background: "linear-gradient(145deg, #0a0a12 0%, #0f1218 35%, #121620 65%, #0a0a12 100%)"Glowing Orbs (Depth Effect)
<div style={{
position: "absolute",
top: -150,
left: -100,
width: 500,
height: 500,
borderRadius: "50%",
background: "radial-gradient(circle, rgba(34,211,238,0.15) 0%, transparent 60%)",
display: "flex",
}} />Gradient Text
<h1 style={{
fontSize: 62,
fontWeight: 800,
background: "linear-gradient(135deg, #ffffff 0%, #e2e8f0 50%, #94a3b8 100%)",
backgroundClip: "text",
color: "transparent",
display: "flex",
}}>Title</h1>SVG Icons with Gradients
<svg width="200" height="200" viewBox="0 0 100 100" fill="none"
style={{ filter: "drop-shadow(0 0 24px rgba(34,211,238,0.35))" }}>
<defs>
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#22d3ee" />
<stop offset="100%" stopColor="#a855f7" />
</linearGradient>
</defs>
<circle cx="50" cy="50" r="45" stroke="url(#grad)" strokeWidth="2" fill="none" />
</svg>Critical Rules
1. Always use `display: "flex"` on every element (Satori requirement) 2. No `gap` on containers without `display: "flex"` 3. Use inline styles only - no CSS classes or external stylesheets 4. All text must be wrapped in elements with explicit styling 5. SVG must use `viewBox` and explicit dimensions 6. Filter property only on SVG root - not on child elements
Color Palette by Section Type
| Section | Primary | Secondary | Accent |
|---|---|---|---|
| Homepage | #3b82f6 blue | #8b5cf6 purple | #06b6d4 cyan |
| About | #10b981 emerald | #14b8a6 teal | #22c55e green |
| Projects | #f97316 orange | #f59e0b amber | #f43f5e rose |
| Writing | #ec4899 pink | #f472b6 rose | #6366f1 indigo |
| Tools/TLDR | #22d3ee cyan | #a855f7 purple | #f472b6 pink |
Badge/Tag Pattern
<div style={{
display: "flex",
padding: "7px 14px",
borderRadius: 8,
background: "rgba(34,211,238,0.15)",
border: "1px solid rgba(34,211,238,0.3)",
}}>
<span style={{ color: "#22d3ee", fontWeight: 600, display: "flex" }}>
Label
</span>
</div>Bottom Gradient Accent
<div style={{
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: 4,
background: "linear-gradient(90deg, transparent 0%, #22d3ee 25%, #a855f7 50%, #f472b6 75%, transparent 100%)",
display: "flex",
}} />Testing
After creating images, run bun run build (or npm run build) to verify routes register as dynamic:
Route (app) Size First Load JS
├ ƒ /opengraph-image 0 B 0 B
├ ƒ /twitter-image 0 B 0 BThe ƒ indicates dynamic (edge) functions.
Common Issues
| Issue | Solution |
|---|---|
| Text not rendering | Add display: "flex" to text wrapper |
| Layout broken | Ensure all containers have display: "flex" |
| Colors wrong | Use hex colors, not CSS variables |
| SVG not showing | Add explicit width/height attributes |
| Gradient text invisible | Need both backgroundClip: "text" AND color: "transparent" |
Extended Patterns
See patterns.md for complete examples including:
- Flywheel diagrams
- Document/writing visuals
- Code window icons
- Network/connection graphics
- Journey path visualizations
Share Image Visual Patterns
Extended patterns for creating distinctive share images.
Flywheel Diagram (Tools/Dashboard)
Circular nodes connected to center hub, representing interconnected tools:
<svg width="210" height="210" viewBox="0 0 100 100" fill="none"
style={{ filter: "drop-shadow(0 0 28px rgba(34,211,238,0.4))" }}>
<defs>
<linearGradient id="ringGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#22d3ee" />
<stop offset="50%" stopColor="#a855f7" />
<stop offset="100%" stopColor="#f472b6" />
</linearGradient>
</defs>
{/* Outer ring */}
<circle cx="50" cy="50" r="45" stroke="url(#ringGrad)" strokeWidth="2" fill="none" opacity="0.8" />
{/* Inner ring */}
<circle cx="50" cy="50" r="35" stroke="#22d3ee" strokeWidth="1" fill="none" opacity="0.4" />
{/* Center hub */}
<circle cx="50" cy="50" r="12" fill="url(#ringGrad)" opacity="0.9" />
<circle cx="50" cy="50" r="8" fill="rgba(255,255,255,0.2)" />
{/* Flywheel nodes - 10 around circle */}
{[0, 36, 72, 108, 144, 180, 216, 252, 288, 324].map((angle, i) => {
const rad = (angle * Math.PI) / 180;
const x = 50 + 40 * Math.cos(rad);
const y = 50 + 40 * Math.sin(rad);
const colors = ["#22d3ee", "#a855f7", "#f472b6", "#22c55e", "#f59e0b",
"#22d3ee", "#a855f7", "#f472b6", "#22c55e", "#f59e0b"];
return (
<g key={i}>
<line x1="50" y1="50" x2={x} y2={y} stroke={colors[i]} strokeWidth="1.5" opacity="0.5" />
<circle cx={x} cy={y} r="5" fill={colors[i]} opacity="0.9" />
<circle cx={x} cy={y} r="3" fill="rgba(255,255,255,0.3)" />
</g>
);
})}
{/* Motion arrows */}
<path d="M50 8 L54 14 L46 14 Z" fill="#22d3ee" opacity="0.7" />
<path d="M92 50 L86 54 L86 46 Z" fill="#a855f7" opacity="0.7" />
<path d="M50 92 L46 86 L54 86 Z" fill="#f472b6" opacity="0.7" />
<path d="M8 50 L14 46 L14 54 Z" fill="#22c55e" opacity="0.7" />
</svg>Document/Writing Visual (Blog/Essays)
Document with text lines, floating cards, and pen icon:
<svg width="185" height="185" viewBox="0 0 100 100" fill="none"
style={{ filter: "drop-shadow(0 0 22px rgba(236,72,153,0.38))" }}>
<defs>
<linearGradient id="writeGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#ec4899" />
<stop offset="50%" stopColor="#f472b6" />
<stop offset="100%" stopColor="#6366f1" />
</linearGradient>
</defs>
{/* Main document frame */}
<rect x="20" y="8" width="60" height="84" rx="4"
stroke="url(#writeGrad)" strokeWidth="2" fill="none" opacity="0.7" />
{/* Document header */}
<rect x="20" y="8" width="60" height="14" rx="4" fill="url(#writeGrad)" opacity="0.12" />
{/* Title line */}
<rect x="28" y="28" width="44" height="5" rx="2.5" fill="#ec4899" opacity="0.8" />
{/* Text lines */}
<rect x="28" y="40" width="44" height="3" rx="1.5" fill="#f472b6" opacity="0.5" />
<rect x="28" y="47" width="38" height="3" rx="1.5" fill="#f472b6" opacity="0.5" />
<rect x="28" y="54" width="42" height="3" rx="1.5" fill="#f472b6" opacity="0.5" />
{/* Floating cards */}
<rect x="5" y="15" width="22" height="30" rx="3" fill="#ec4899" opacity="0.15" />
<rect x="75" y="35" width="20" height="28" rx="3" fill="#f472b6" opacity="0.15" />
{/* Pen icon */}
<path d="M85 8 L92 15 L78 29 L74 30 L75 26 Z" fill="#ec4899" opacity="0.7" />
<path d="M87 6 L94 13 L92 15 L85 8 Z" fill="#6366f1" opacity="0.8" />
</svg>Code Window (Projects/Dev Tools)
Terminal-style window with code lines and git branch:
<svg width="210" height="210" viewBox="0 0 100 100" fill="none"
style={{ filter: "drop-shadow(0 0 22px rgba(249,115,22,0.38))" }}>
<defs>
<linearGradient id="projGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#f97316" />
<stop offset="50%" stopColor="#f59e0b" />
<stop offset="100%" stopColor="#f43f5e" />
</linearGradient>
</defs>
{/* Main window */}
<rect x="10" y="15" width="80" height="70" rx="6"
stroke="url(#projGrad)" strokeWidth="2.5" fill="none" opacity="0.75" />
{/* Title bar */}
<rect x="10" y="15" width="80" height="12" rx="6" fill="url(#projGrad)" opacity="0.18" />
{/* Window dots */}
<circle cx="18" cy="21" r="3" fill="#f43f5e" opacity="0.9" />
<circle cx="27" cy="21" r="3" fill="#f59e0b" opacity="0.9" />
<circle cx="36" cy="21" r="3" fill="#22c55e" opacity="0.9" />
{/* Code lines */}
<rect x="18" y="36" width="38" height="3.5" rx="1.75" fill="#f97316" opacity="0.75" />
<rect x="22" y="45" width="52" height="3.5" rx="1.75" fill="#f59e0b" opacity="0.55" />
<rect x="22" y="54" width="42" height="3.5" rx="1.75" fill="#f59e0b" opacity="0.55" />
<rect x="18" y="63" width="30" height="3.5" rx="1.75" fill="#f97316" opacity="0.75" />
{/* Git branch */}
<path d="M5 38 Q 8 50, 5 62" stroke="#f97316" strokeWidth="2.5" fill="none" opacity="0.55" />
<circle cx="5" cy="38" r="3.5" fill="#f97316" opacity="0.85" />
<circle cx="5" cy="62" r="3.5" fill="#f59e0b" opacity="0.85" />
</svg>Network Hexagon (AI/Tech)
Hexagonal network representing connections:
<svg width="200" height="200" viewBox="0 0 100 100" fill="none"
style={{ filter: "drop-shadow(0 0 25px rgba(59,130,246,0.4))" }}>
<defs>
<linearGradient id="hexGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#3b82f6" />
<stop offset="50%" stopColor="#8b5cf6" />
<stop offset="100%" stopColor="#06b6d4" />
</linearGradient>
</defs>
{/* Main hexagon */}
<polygon points="50,5 90,27 90,73 50,95 10,73 10,27"
stroke="url(#hexGrad)" strokeWidth="2" fill="none" opacity="0.8" />
{/* Inner hexagon */}
<polygon points="50,20 75,35 75,65 50,80 25,65 25,35"
stroke="url(#hexGrad)" strokeWidth="1.5" fill="none" opacity="0.4" />
{/* Center core */}
<circle cx="50" cy="50" r="12" fill="url(#hexGrad)" opacity="0.9" />
<circle cx="50" cy="50" r="7" fill="rgba(255,255,255,0.25)" />
{/* Corner nodes */}
<circle cx="50" cy="5" r="4" fill="#3b82f6" opacity="0.9" />
<circle cx="90" cy="27" r="4" fill="#8b5cf6" opacity="0.9" />
<circle cx="90" cy="73" r="4" fill="#06b6d4" opacity="0.9" />
<circle cx="50" cy="95" r="4" fill="#3b82f6" opacity="0.9" />
<circle cx="10" cy="73" r="4" fill="#8b5cf6" opacity="0.9" />
<circle cx="10" cy="27" r="4" fill="#06b6d4" opacity="0.9" />
{/* Connection lines to center */}
<line x1="50" y1="50" x2="50" y2="5" stroke="#3b82f6" strokeWidth="1" opacity="0.5" />
<line x1="50" y1="50" x2="90" y2="27" stroke="#8b5cf6" strokeWidth="1" opacity="0.5" />
<line x1="50" y1="50" x2="90" y2="73" stroke="#06b6d4" strokeWidth="1" opacity="0.5" />
</svg>Journey Path (About/Career)
Path with milestone dots for timelines:
<svg width="200" height="200" viewBox="0 0 100 100" fill="none"
style={{ filter: "drop-shadow(0 0 22px rgba(16,185,129,0.38))" }}>
<defs>
<linearGradient id="pathGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#10b981" />
<stop offset="50%" stopColor="#14b8a6" />
<stop offset="100%" stopColor="#22c55e" />
</linearGradient>
</defs>
{/* Main path */}
<path d="M10,85 Q25,70 30,50 T50,30 T70,50 T90,15"
stroke="url(#pathGrad)" strokeWidth="3" fill="none" opacity="0.8"
strokeLinecap="round" />
{/* Milestone dots */}
<circle cx="10" cy="85" r="6" fill="#10b981" opacity="0.95" />
<circle cx="30" cy="50" r="6" fill="#14b8a6" opacity="0.95" />
<circle cx="50" cy="30" r="6" fill="#22c55e" opacity="0.95" />
<circle cx="70" cy="50" r="6" fill="#14b8a6" opacity="0.95" />
<circle cx="90" cy="15" r="6" fill="#10b981" opacity="0.95" />
{/* Inner dot highlights */}
<circle cx="10" cy="85" r="3" fill="rgba(255,255,255,0.3)" />
<circle cx="50" cy="30" r="3" fill="rgba(255,255,255,0.3)" />
<circle cx="90" cy="15" r="3" fill="rgba(255,255,255,0.3)" />
</svg>Grid Pattern Background
Subtle grid overlay for depth:
<div style={{
position: "absolute",
top: 0, left: 0, right: 0, bottom: 0,
opacity: 0.025,
backgroundImage: `url("data:image/svg+xml,%3Csvg width='40' height='40' viewBox='0 0 40 40' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' stroke='%2322d3ee' stroke-width='0.5'%3E%3Cpath d='M0 20h40M20 0v40'/%3E%3C/g%3E%3C/svg%3E")`,
display: "flex",
}} />Change the stroke color (%2322d3ee) to match your theme.
Two-Column Layout
Standard layout with visual left, text right:
<div style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 60,
padding: "40px 60px",
width: "100%",
}}>
{/* Left - Visual */}
<div style={{ display: "flex", position: "relative" }}>
{/* Glow behind icon */}
<div style={{
position: "absolute",
width: 270, height: 270,
borderRadius: "50%",
background: "radial-gradient(circle, rgba(34,211,238,0.25) 0%, transparent 70%)",
filter: "blur(30px)",
display: "flex",
}} />
{/* SVG Icon */}
</div>
{/* Right - Text */}
<div style={{ display: "flex", flexDirection: "column", maxWidth: 640 }}>
{/* Badge */}
{/* Title */}
{/* Subtitle */}
{/* Description */}
{/* Tags */}
</div>
</div>URL Badge (Top Right)
<div style={{
position: "absolute",
top: 28,
right: 38,
fontSize: 14,
color: "#475569",
display: "flex",
}}>
<span style={{ display: "flex" }}>example.com/page</span>
</div>