# Social Images Need Absolute URLs

**MUST** · **ID:** `design-og-image-absolute-url` · **Category:** design
**Source:** [@Ibelick](https://www.ui-skills.com/)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/design-og-image-absolute-url

> MUST: `og:image` and `twitter:image` must be ABSOLUTE URLs. A relative `/og.png` resolves fine in your browser (it has a current document) and ships a blank grey card on Slack, iMessage and LinkedIn, which fetch the HTML and have no origin to resolve against — and nothing in your build, typecheck, or local preview complains. Build the URL from one site-wide base constant. Ship a stable 1200x630 image and set `twitter:card` to `summary_large_image`, or the artwork renders at favicon size.

og:image and twitter:image must be absolute URLs, because a relative path renders a blank card everywhere the link is actually shared

From the social cards rules in ibelick's fixing-metadata skill. This is the single most common social-preview bug, and its defining property is that it is invisible during development. `<meta property="og:image" content="/og.png">` resolves fine in your browser, because your browser has a current document to resolve it against. Slack, iMessage, LinkedIn, and every other unfurler do not: they fetch your HTML, read the tag, and get a path with no origin to attach it to. Some drop it, some guess, all of them are entitled to give up — so the link ships as a blank grey card, and nothing in your build, your typecheck, or your local preview ever complains. The rule is to emit the full origin, which in practice means building the URL from one site-wide base constant (`${SITE_URL}/og.png`) rather than trusting each page to remember. Two neighbours in the same rule group matter as much: the image wants a stable, correct aspect (1200 x 630 is the safe default that survives every crop), and `twitter:card` wants `summary_large_image`, without which the card renders as a thumbnail and the artwork you commissioned appears the size of a favicon.

## Rule snippet

```tsx
<meta property="og:image" content={`${SITE_URL}/og.png`} />
<meta name="twitter:card" content="summary_large_image" />
```

## Bad — do not do this

`design-og-image-absolute-url-bad`

```tsx
export function OgImageAbsoluteUrlBad() {
  return (
    <div className="w-full space-y-4">
      {/* Mock Slack / iMessage share card */}
      <div className="rounded-lg border border-border bg-card p-3">
        <p className="mb-2 text-xs text-muted-foreground">Shared in Slack</p>
        <div className="overflow-hidden rounded-lg border border-border">
          <div className="flex aspect-[1200/630] items-center justify-center bg-muted">
            <span className="text-xs text-muted-foreground">
              image failed to load
            </span>
          </div>
          <div className="bg-background p-3">
            <p className="text-xs text-muted-foreground">myapp.com</p>
            <p className="text-sm font-medium text-foreground">Pricing — MyApp</p>
          </div>
        </div>
      </div>

      <pre className="overflow-x-auto rounded-lg bg-muted p-3 text-xs text-foreground"><code>{`<meta property="og:image" content="/og.png" />`}</code></pre>

      <p className="text-xs text-error">
        Renders fine in dev, blank grey card everywhere else. The crawler has no page to resolve
        "/og.png" against, so it drops the image — and nothing in your build ever tells you.
      </p>
    </div>
  );
}
```

## Good — do this

`design-og-image-absolute-url-good`

```tsx
export function OgImageAbsoluteUrlGood() {
  return (
    <div className="w-full space-y-4">
      {/* Mock Slack / iMessage share card */}
      <div className="rounded-lg border border-border bg-card p-3">
        <p className="mb-2 text-xs text-muted-foreground">Shared in Slack</p>
        <div className="overflow-hidden rounded-lg border border-border">
          <div className="flex aspect-[1200/630] flex-col items-center justify-center gap-1 bg-accent">
            <span className="text-base font-semibold text-accent-foreground">MyApp</span>
            <span className="text-xs text-accent-foreground">Pricing that scales with you</span>
          </div>
          <div className="bg-background p-3">
            <p className="text-xs text-muted-foreground">myapp.com</p>
            <p className="text-sm font-medium text-foreground">Pricing — MyApp</p>
          </div>
        </div>
        <p className="mt-2 text-xs text-muted-foreground">1200 × 630 — summary_large_image</p>
      </div>

      <pre className="overflow-x-auto rounded-lg bg-muted p-3 text-xs text-foreground"><code>{`<meta property="og:image" content="https://myapp.com/og.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta name="twitter:card" content="summary_large_image" />`}</code></pre>

      <p className="text-xs text-success">
        Absolute URL, stable 1200 × 630 aspect, twitter:card set. Build the URL from one
        site-wide base constant so no page can forget the origin.
      </p>
    </div>
  );
}
```

## References

- [fixing-metadata (skill source)](https://raw.githubusercontent.com/ibelick/ui-skills/main/skills/fixing-metadata/SKILL.md)
- [The Open Graph protocol](https://ogp.me/)
- [X: Summary card with large image](https://developer.x.com/en/docs/x-for-websites/cards/overview/summary-card-with-large-image)
