From a24a646e7ab92e28ca52252e13a6d917b673e3ac Mon Sep 17 00:00:00 2001
From: Eric Seidel <eric@shorebird.dev>
Date: Thu, 30 Jan 2025 13:57:47 -0800
Subject: [PATCH] feat: add more og tags to our blogs (#240)

* feat: add more og tags to our blogs

This includes the twitter tags:
https://developer.x.com/en/docs/x-for-websites/cards/guides/getting-started
And the "article" type tags:
https://ogp.me/#type_article

* fix: make image urls absolute

* chore: move comment and fix cspell
---
 src/components/Author.astro  |  9 ++++++++-
 src/layouts/BlogLayout.astro | 30 +++++++++++++++++++++++++-----
 2 files changed, 33 insertions(+), 6 deletions(-)

diff --git a/src/components/Author.astro b/src/components/Author.astro
index 81b002b2..402f6f6f 100644
--- a/src/components/Author.astro
+++ b/src/components/Author.astro
@@ -4,31 +4,37 @@ import felixHeadshot from '../assets/images/felix-headshot.jpeg';
 import bryanHeadshot from '../assets/images/bryan-headshot.png';
 import shorebirdHeadshot from '../assets/images/shorebird-headshot.png';
 
+// cspell:words shorebirddev felangelov
+
 interface Author {
   name: string;
   title: string;
   avatar: ImageMetadata;
   github: string;
+  twitter?: string;
 }
 
-const authors: Record<string, Author> = {
+export const authors: Record<string, Author> = {
   shorebirdtech: {
     name: 'Shorebird',
     title: 'The Shorebird Team',
     avatar: shorebirdHeadshot,
     github: 'shorebirdtech',
+    twitter: '@shorebirddev',
   },
   eseidel: {
     name: 'Eric Seidel',
     title: 'Founder & CEO',
     avatar: ericHeadshot,
     github: 'eseidel',
+    twitter: '@_eseidel',
   },
   felangel: {
     name: 'Felix Angelov',
     title: 'Founding Engineer',
     avatar: felixHeadshot,
     github: 'felangel',
+    twitter: '@felangelov',
   },
   bryanoltman: {
     name: 'Bryan Oltman',
@@ -40,6 +46,7 @@ const authors: Record<string, Author> = {
 
 const { handle } = Astro.props;
 const author = authors[handle];
+
 ---
 
 <div class="flex items-center">
diff --git a/src/layouts/BlogLayout.astro b/src/layouts/BlogLayout.astro
index 58ba3190..6d4edb46 100644
--- a/src/layouts/BlogLayout.astro
+++ b/src/layouts/BlogLayout.astro
@@ -1,4 +1,5 @@
 ---
+// cspell:words shorebirddev
 import '@fontsource/inter';
 import '@fontsource/inter/500.css';
 import '@fontsource/inter/600.css';
@@ -6,7 +7,7 @@ import '@fontsource/inter/700.css';
 import '@fontsource/inter/800.css';
 import '@fontsource/inter/900.css';
 import { config } from '~/config';
-import Author from '~/components/Author.astro';
+import Author, { authors } from '~/components/Author.astro';
 import FormattedDate from '~/components/FormattedDate.astro';
 import { Navbar } from '~/components/Navbar';
 import { Footer } from '~/components/Footer';
@@ -15,6 +16,15 @@ import { BlogFooterPitch } from '~/components/BlogFooterPitch';
 import '../styles/Theme.css';
 
 const { title, description, author, date, cover, coverAlt } = Astro.props;
+
+// og:image is expected to be an absolute url beginning with http or https
+const coverUrl = new URL(cover ? cover : '/open-graph.png', Astro.url.origin);
+// Always use Astro.site (production url) rather than a test domain.
+const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
+// See also https://ogp.me/#type_article
+// Could also add og:tags here if we added them to the frontmatter for the
+// individual blog posts.  I'm not sure what, if any, sites crawl for the
+// og:tags property.
 ---
 
 <!doctype html>
@@ -27,12 +37,22 @@ const { title, description, author, date, cover, coverAlt } = Astro.props;
     <meta name="generator" content={Astro.generator} />
     <meta name="description" content={description} />
     <meta property="og:description" content={description} />
-    <meta property="og:image" content={cover ? cover : '/open-graph.png'} />
+    <meta property="og:image" content={coverUrl} />
     <meta property="og:url" content={Astro.site} />
     <meta property="og:title" content={title} />
-    <meta name="twitter:image" content={cover ? cover : '/open-graph.png'} />
+    <meta name="twitter:image" content={coverUrl} />
     <meta name="twitter:card" content="summary_large_image" />
-    <link rel="canonical" href={new URL(Astro.url.pathname, Astro.site)} />
+    <link rel="canonical" href={canonicalUrl} />
+    <meta property="og:type" content="article" />
+    <meta property="article:published_time" content={date} />
+    <meta property="article:author" content={author} />
+    <meta property="article:section" content="Technology" />
+    <meta name="twitter:site" content="@shorebirddev" />
+    {
+      authors[author].twitter && (
+        <meta name="twitter:creator" content={authors[author].twitter} />
+      )
+    }
     <script
       defer
       data-domain="shorebird.dev"
@@ -56,7 +76,7 @@ const { title, description, author, date, cover, coverAlt } = Astro.props;
       }
       <hr class="my-8" />
       <slot />
-      <BlogFooterPitch />      
+      <BlogFooterPitch />
     </div>
     <Footer client:load />
     <style is:global>