// alternatives

The Vercel OG Alternative for Real HTML/CSS

Vercel OG is great for simple cards. But once you need your actual page as an OG image — not a JSX approximation of it — Satori's limitations start to hurt. Linkshot screenshots your live site directly.

Try Linkshot free7-day trial, no credit card required

What is Vercel OG?

@vercel/og is an Edge Runtime utility built on top of Satori — an open-source library that converts JSX to SVG and then to PNG. It's well-integrated with Next.js App Router (opengraph-image.tsx) and is free to use.

For simple, text-heavy cards with basic layout it works well. The limitations appear when your design gets complex, or when you want to use your existing page as the OG image instead of a bespoke template.

Where Vercel OG falls short

You have to rebuild your page in JSX

Vercel OG (via Satori) does not screenshot your actual page. You write a separate JSX template that mimics your design. Every time your real page changes, your OG template drifts.

CSS is a limited subset — flexbox only

Satori renders a subset of CSS. No CSS Grid, limited positioning, no arbitrary TailwindCSS values, no backdrop filters, no complex pseudo-elements. You find out what breaks at runtime.

Font loading is manual and fragile

You must fetch font files manually, pass them as ArrayBuffers, and register each weight separately. Google Fonts, variable fonts, and system fonts require extra wiring.

Tightly coupled to the Vercel / Next.js ecosystem

Works great if you're on Next.js and deploying to Vercel. For Remix, Astro, SvelteKit, or self-hosted setups, integration is more involved and there's less documentation.

Complex layouts break unpredictably

Satori converts JSX to SVG. The conversion is imperfect — shadows, gradients, multi-line text truncation, and nested flex containers often produce unexpected output.

The fundamental difference

Vercel OG asks you to rebuild your design as a JSX template. You create a parallel version of your page — in a different language, with a different CSS model — and keep it in sync manually.

Linkshot takes a different approach: it screenshots your actual live page using a real browser. Your OG image is always in sync because it is your page. No templates to build. No CSS subset to learn.

@vercel/og — opengraph-image.tsx

import { ImageResponse } from 'next/og'

export const size = { width: 1200, height: 630 }
export const contentType = 'image/png'

export default async function Image({ params }) {
  const post = await getPost(params.slug)

  // ⚠ Fonts must be fetched manually
  const inter = await fetch(
    new URL('./Inter-Bold.ttf', import.meta.url),
  ).then((r) => r.arrayBuffer())

  return new ImageResponse(
    (
      // ⚠ Rebuild your hero design in JSX
      // ⚠ Flexbox only — no CSS Grid
      // ⚠ TailwindCSS class support is experimental + subset
      <div style={{ display: 'flex', /* ...repeat your hero CSS here... */ }}>
        <h1 style={{ fontSize: 64 }}>{post.title}</h1>
        <p style={{ fontSize: 24 }}>{post.excerpt}</p>
      </div>
    ),
    {
      ...size,
      fonts: [{ name: 'Inter', data: inter, weight: 700 }],
    },
  )
}

Linkshot — one meta tag

{/* In your <head> */}
<meta
  property="og:image"
  content={`https://uselinkshot.com/api/og/v1/${TEMPLATE_ID}?url=${pageUrl}`}
/>

{/* Optional: hide nav and cookie banner only in the screenshot */}
<nav className="linkshot:hidden">…</nav>
<div className="cookie-banner linkshot:hidden">…</div>

{/* Optional: restyle the hero heading only at capture time */}
<h1 className="text-3xl linkshot:text-6xl linkshot:text-center">
  {post.title}
</h1>

// ✓ Real Chromium render — every CSS feature works
// ✓ Your real TailwindCSS config — arbitrary values, plugins, JIT
// ✓ Your real fonts — already loaded by your page
// ✓ Always in sync with your design
// ✓ Same code on Next, Astro, Remix, SvelteKit, Hono, anywhere

Hide nav, sidebars, and overlays — only in the screenshot

Add the linkshot: modifier to any element. It's active only during screenshot capture — your live page is untouched.

<nav class="linkshot:hidden"> … </nav>

Feature comparison

FeatureVercel OGLinkshot
Screenshots your real live page
Real Chromium browser render
JavaScript executes before capture
Full TailwindCSS utilities (no subset)partial
CSS Grid
Container queries
Backdrop filters / pseudo-elementslimited
CSS variables & custom propertieslimited
Automatic font loading (incl. Google Fonts)
Variable fontslimited
Emoji & non-Latin scripts out of the boxmanual
No separate JSX template to maintain
OG stays in sync when you ship a redesign
Screenshot-only styling (linkshot: modifier)
Per-page templates without code changes
Browser extension for live preview
Works with any frameworkNext.js-first
Domain allowlist (security)manual
Free tier / trialOSS, free7-day trial, no card
Self-hostable

Why developers switch from @vercel/og to Linkshot

We built Linkshot after watching the same five frustrations come up in every team that scales an OG-image setup past the first card. None of them are dealbreakers in isolation — together they are why switching feels like relief, not a trade.

01The drift problem

Every team that ships @vercel/og hits the same silent failure within a quarter. You launch your site with a beautiful OG image — JSX template carefully crafted to match the hero. Three weeks later marketing tweaks the headline, redesigns the gradient, swaps the product screenshot. The site looks great. The OG image still shows the old version.

Nobody catches it because nobody looks at OG images after launch. The Slack preview keeps showing the wrong gradient, the wrong headline, the wrong screenshot — for months. By the time someone notices, you have to dig up the JSX template, port the new design into a CSS subset, re-test on Satori, redeploy.

Linkshot makes drift impossible by construction. Your OG image is a screenshot of your live page. When you redesign the hero, the OG updates on the next cache cycle. There is no second copy of your design to keep in sync.

02The CSS subset wall

Satori — the library @vercel/og is built on — implements a strict subset of CSS. Flexbox only, no Grid. No CSS variables in many positions. Limited backdrop filters. Pseudo-elements that work in Chrome silently break in the JSX-to-SVG pipeline. Container queries are a non-starter.

If your site uses modern TailwindCSS — arbitrary values like w-[42rem], the typography plugin, container queries, dark mode with CSS variables — you will hit the wall. Each workaround means duplicating layout logic in a constrained dialect of CSS that exists nowhere else in your stack.

Linkshot uses a real headless Chromium. If Chrome can render it, Linkshot captures it. Every TailwindCSS utility, every CSS feature, every JavaScript-driven layout works the same way it does in your browser dev tools.

03Font and asset fragility

@vercel/og does not load your fonts for you. You fetch the .ttf or .otf file at runtime, pass it as an ArrayBuffer, register every weight separately, and pray Satori handles your fallback chain. Variable fonts work in narrow circumstances. Emoji and non-Latin scripts require extra wiring. System fonts simply do not exist.

When something looks wrong, the failure mode is silent: text shifts, weights collapse to 400, ligatures vanish — no warnings, no console errors. You debug by trial and error.

Linkshot inherits everything from your real page. The fonts your site loads are the fonts the screenshot uses. Google Fonts, self-hosted .woff2, variable axes, emoji rendering, RTL scripts — all already configured because they are configured for your visitors.

04Per-page templating that does not scale

The first @vercel/og card is exciting. The fourth one — different layout for blog posts, different for changelog, different for docs, different for marketing pages — is a chore. You end up with four near-identical JSX components, each fetching its own data, each maintaining its own design subset, each broken in slightly different ways.

Linkshot's template system inverts this. You register a domain once, define a template (viewport, scale factor, output format, TailwindCSS injection rules) once, and the same template applies across every page on that domain. Want different OG treatment for /blog versus /pricing? Use two templates. The page itself stays the source of truth — templates only specify how to capture it.

05What you get back

Switching to Linkshot from @vercel/og typically reclaims a few hundred lines of JSX, a runtime font-loading helper, the entire CSS-subset memorization tax, and the silent drift problem. You replace it all with a single hosted URL and an optional class modifier (linkshot:) for cases where you want to hide a nav bar or restyle a heading only at capture time.

The trade-off is honest: Linkshot is a paid service ($9/mo entry tier, 7-day trial without a card), where @vercel/og is free OSS. If your only need is a static text card and you are happy with Satori's constraints, stay on @vercel/og. If you want your real design as the OG image — and you want it to keep working when you redesign — Linkshot is the simpler answer.

Which one should you use?

Stick with Vercel OG if…

  • You're on Next.js and your OG cards are simple text + logo
  • You need something free with zero API calls
  • Your design is basic enough to fit in a single JSX component

Use Linkshot if…

  • You want your real page as the OG image — no extra templates
  • Your layout uses CSS Grid, complex TailwindCSS, or custom fonts
  • You need one template to work across every page automatically
  • You're on Remix, Astro, SvelteKit, or any non-Next.js stack

Frequently asked questions

Is Linkshot a drop-in replacement for @vercel/og?

It depends on your setup. If your only OG need is a static text-and-logo card and you're already on Vercel, @vercel/og is fine and free. If you want your real live page as the OG image, full TailwindCSS, no Satori CSS subset, and a hosted service that works on any framework — switching to Linkshot is a one-meta-tag change.

Can I use @vercel/og for some pages and Linkshot for others?

Yes. Linkshot generates the URL you put in your <meta property="og:image">. You can mix and match per route — Linkshot for marketing/blog pages where the live design matters, @vercel/og for routes where a JSX template is genuinely simpler.

How does pricing compare?

@vercel/og is free, but you pay Vercel for function invocations and bandwidth, plus engineering time to build and maintain templates. Linkshot starts at a flat monthly fee with a 7-day free trial (no credit card), edge caching, and zero template engineering.

Does Linkshot work outside Next.js or Vercel?

Yes. Linkshot is a hosted API — your meta tag is just a URL. It works identically on Astro, Remix, SvelteKit, Nuxt, TanStack Start, Hono, plain HTML, WordPress, Shopify, Webflow, Framer, or any backend that can render a <meta> tag.

What about cold-start performance?

Linkshot screenshots are rendered at the edge and cached on a global CDN. The first hit for a given URL warms the cache; every subsequent share fetches from the nearest edge in milliseconds. Social-media crawlers (Facebook, X, LinkedIn, Slack) hit the cached version.

My page uses CSS Grid / container queries / arbitrary TailwindCSS values. Will it render?

Yes. Linkshot uses a real Chromium browser, so anything Chrome can render, Linkshot can capture. Satori (the engine behind @vercel/og) supports a flexbox-only CSS subset; many real-world layouts hit its limits.

Can I hide my navbar, cookie banner, or chat widget in the screenshot?

Yes — that is exactly what the linkshot: TailwindCSS modifier is for. Add class="linkshot:hidden" to any element and it disappears only during screenshot capture. Your live site is untouched.

What happens when I change my page design?

Your OG image updates automatically. That's the core difference: with @vercel/og, you redesign your hero and your OG quietly drifts to the old look until you remember to update the JSX template. With Linkshot, the OG image is the page — they cannot drift apart.

Ready to switch?

Add your domain, create a template, drop in one <meta> tag. Your first OG image in minutes.

7-day free trial · no credit card required