diff --git a/site/public/og-image.jpg b/site/public/og-image.jpg index 66b64ecea..4fb361dc7 100644 Binary files a/site/public/og-image.jpg and b/site/public/og-image.jpg differ diff --git a/site/scripts/gen-icons.mjs b/site/scripts/gen-icons.mjs index 99a903084..38556c071 100644 --- a/site/scripts/gen-icons.mjs +++ b/site/scripts/gen-icons.mjs @@ -9,8 +9,9 @@ * web-app-manifest-192x192.png * web-app-manifest-512x512.png * - * The 1200x600 OG / Twitter card (public/og-image.jpg) is exported from the - * Figma Branding file (the "Social" frame), not generated here. + * The 1920x1008 OG / Twitter card (public/og-image.jpg) is a top-left crop of + * the 1920x1080 Figma "Social" frame export, not generated here. The crop is + * to 1.91:1 (1200x630 x1.6), so the frame's top-left never gets cut. * * Run with: node scripts/gen-icons.mjs */ diff --git a/site/src/layouts/BlogPost.astro b/site/src/layouts/BlogPost.astro index 896e94932..4773658a3 100644 --- a/site/src/layouts/BlogPost.astro +++ b/site/src/layouts/BlogPost.astro @@ -22,6 +22,8 @@ const canonical = new URL(Astro.url.pathname, Astro.site ?? 'https://warmbly.com // Per-post social/share image: use the post cover when set, else the site card. const shareImage = cover ?? '/og-image.jpg'; const shareImageAbsolute = new URL(shareImage, Astro.site ?? 'https://warmbly.com').toString(); +// Covers in public/blog are all exported at 1200x600; the site card is 1920x1008. +const shareImageSize = cover ? { w: 1200, h: 600 } : { w: 1920, h: 1008 }; const articleJsonLd = { '@context': 'https://schema.org', @@ -61,7 +63,7 @@ const neighbors = [ next ? { label: 'Up next', post: next } : null, ].filter(Boolean) as { label: string; post: BlogPost }[]; --- - +
diff --git a/site/src/layouts/Layout.astro b/site/src/layouts/Layout.astro index 8684b6a09..890e9bd34 100644 --- a/site/src/layouts/Layout.astro +++ b/site/src/layouts/Layout.astro @@ -8,6 +8,10 @@ interface Props { title: string; description?: string; ogImage?: string; + /** Real pixel size of ogImage; scrapers render the card before fetching it. */ + ogImageWidth?: number; + ogImageHeight?: number; + ogImageAlt?: string; /** og:type — 'website' for most pages, 'article' for Learn essays */ ogType?: string; /** Extra structured data injected per-page (e.g. BlogPosting, SoftwareApplication) */ @@ -21,6 +25,9 @@ const { title, description = 'Warmbly is the deliverability platform for cold email. Warm your inboxes, send at scale, keep your reputation clean.', ogImage = '/og-image.jpg', + ogImageWidth = 1920, + ogImageHeight = 1008, + ogImageAlt = 'The Warmbly dashboard, showing the unified inbox across connected mailboxes', ogType = 'website', jsonLd, noIndex = false, @@ -42,6 +49,12 @@ const posthogUiHost = import.meta.env.PUBLIC_POSTHOG_UI_HOST ?? 'https://us.post const siteOrigin = (Astro.site ?? new URL('https://warmbly.com')).toString().replace(/\/$/, ''); const canonical = new URL(Astro.url.pathname, Astro.site ?? 'https://warmbly.com').toString(); const ogImageAbsolute = new URL(ogImage, Astro.site ?? 'https://warmbly.com').toString(); +// Blog covers are .webp; declaring them as jpeg makes some scrapers skip the card. +const ogImageType = ogImage.endsWith('.webp') + ? 'image/webp' + : ogImage.endsWith('.png') + ? 'image/png' + : 'image/jpeg'; // Schema.org Organization payload, sourced from the real company record. const organizationJsonLd = { @@ -125,10 +138,10 @@ const websiteJsonLd = { - - - - + + + + @@ -137,7 +150,7 @@ const websiteJsonLd = { - +