diff --git a/.github/workflows/deploy-beta.yml b/.github/workflows/deploy-beta.yml
index 91e79fe..1dd6827 100644
--- a/.github/workflows/deploy-beta.yml
+++ b/.github/workflows/deploy-beta.yml
@@ -29,6 +29,8 @@ jobs:
- run: npm ci
- run: npm run check
- run: npm run build:site
+ env:
+ SOCIAL_IMAGE_ORIGIN: https://beta.perryts.com
- run: npm run check:blog
- name: Deploy beta
diff --git a/README.md b/README.md
index 8c58fbd..b20bdd0 100644
--- a/README.md
+++ b/README.md
@@ -121,6 +121,8 @@ The Nginx configuration is in `deploy/nginx-beta.conf`, installed at
`/etc/nginx/sites-available/beta.perryts.com` and linked from `sites-enabled`.
It uses the existing Certbot certificate, redirects `/` to `/en/`, handles clean
URLs, and excludes beta from indexing. Canonical URLs still point to production.
+Beta builds set `SOCIAL_IMAGE_ORIGIN=https://beta.perryts.com` so social previews
+fetch beta's image. Builds without this override use the production origin.
Newsletter requests use a separate `perryts-beta-subscribe` systemd service on
loopback port 3852 (`deploy/perryts-beta-subscribe.service`). It reads the existing
diff --git a/deploy-beta.sh b/deploy-beta.sh
index 2e9fa45..98b6afc 100644
--- a/deploy-beta.sh
+++ b/deploy-beta.sh
@@ -7,7 +7,7 @@ REMOTE_DIR="/var/www/beta.perryts.com"
RELEASE="$(date -u +%Y%m%dT%H%M%SZ)-$(git rev-parse --short HEAD)"
case "${1:-}" in
- "") npm run build:site ;;
+ "") SOCIAL_IMAGE_ORIGIN=https://beta.perryts.com npm run build:site ;;
--skip-build) ;; # CI has already built and checked this checkout.
*) echo "Usage: $0 [--skip-build]" >&2; exit 2 ;;
esac
diff --git a/public/og.png b/public/og.png
index c647f25..2afbdf0 100644
Binary files a/public/og.png and b/public/og.png differ
diff --git a/public/perry-social-banner.svg b/public/perry-social-banner.svg
index d37a15e..d0328e8 100644
--- a/public/perry-social-banner.svg
+++ b/public/perry-social-banner.svg
@@ -33,5 +33,5 @@
Perry
- TypeScript → Native
+ TypeScript/JavaScript → Native
\ No newline at end of file
diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro
index 73537ab..b57d269 100644
--- a/src/layouts/Base.astro
+++ b/src/layouts/Base.astro
@@ -3,7 +3,7 @@ import "../styles/global.css";
import Nav from "../components/Nav.astro";
import Footer from "../components/Footer.astro";
import { locales, localePath, type Locale } from "../i18n/index.ts";
-import { metaDescription, pageTitle } from "../lib/seo.ts";
+import { DEFAULT_SOCIAL_IMAGE, metaDescription, pageTitle, socialImageUrl } from "../lib/seo.ts";
interface Props {
lang: Locale;
@@ -33,15 +33,17 @@ const {
titleIsAbsolute = false,
canonical,
alternates = locales,
- image = "/og.png",
+ image = DEFAULT_SOCIAL_IMAGE,
type = "website",
publishedTime,
noindex = false,
} = Astro.props;
const fullTitle = pageTitle(title, titleIsAbsolute);
const descriptionText = metaDescription(description);
+const socialTitle = fullTitle.replaceAll("JavaScript/TypeScript", "TypeScript/JavaScript");
+const socialDescription = descriptionText.replaceAll("JavaScript/TypeScript", "TypeScript/JavaScript");
const canonicalUrl = new URL(canonical ?? localePath(lang, path), Astro.site);
-const imageUrl = new URL(image, Astro.site);
+const imageUrl = socialImageUrl(image, Astro.site);
const ogLocale = lang.replace("-", "_");
---
@@ -62,19 +64,21 @@ const ogLocale = lang.replace("-", "_");
-
-
+
+
-
-
+
+
+ {image === DEFAULT_SOCIAL_IMAGE && }
-
-
+
+
{publishedTime && }
+ {image === DEFAULT_SOCIAL_IMAGE && }
diff --git a/src/lib/seo.ts b/src/lib/seo.ts
index e989120..00a1c87 100644
--- a/src/lib/seo.ts
+++ b/src/lib/seo.ts
@@ -2,6 +2,13 @@ const BRAND = " — Perry";
const TITLE_MAX = 60;
const DESCRIPTION_MAX = 160;
+export const DEFAULT_SOCIAL_IMAGE = "/og.png?v=typescript-javascript";
+
+/** Beta serves its own preview image while canonical URLs remain on production. */
+export function socialImageUrl(image: string, site: URL | undefined): URL {
+ return new URL(image, import.meta.env.SOCIAL_IMAGE_ORIGIN || site);
+}
+
/**
* "
— Perry". The brand is dropped when the title already names Perry or when it would push
* the title past what search results show.
diff --git a/src/pages/[lang]/blog/[slug]/index.astro b/src/pages/[lang]/blog/[slug]/index.astro
index 39a0f71..0abc893 100644
--- a/src/pages/[lang]/blog/[slug]/index.astro
+++ b/src/pages/[lang]/blog/[slug]/index.astro
@@ -14,6 +14,7 @@ import { messages, plural } from "../../../../i18n/messages.ts";
import { locales, localePath, type Locale } from "../../../../i18n/index.ts";
import { formatPostDate, getPosts, type Post } from "../../../../lib/blog.ts";
import { readingMinutes } from "../../../../lib/reading-time.ts";
+import { DEFAULT_SOCIAL_IMAGE, socialImageUrl } from "../../../../lib/seo.ts";
// Every post exists under every locale, like the previous site; untranslated ones show English with a notice.
export async function getStaticPaths() {
@@ -56,7 +57,7 @@ const blogPosting = {
keywords: entry.data.tags.join(", "),
url,
mainEntityOfPage: url,
- image: new URL("/og.png", Astro.site).toString(),
+ image: socialImageUrl(DEFAULT_SOCIAL_IMAGE, Astro.site).toString(),
author: { "@type": "Organization", name: "Perry", url: "https://perryts.com/" },
publisher: { "@type": "Organization", name: "Perry", logo: { "@type": "ImageObject", url: new URL("/perry-icon.svg", Astro.site).toString() } },
};