From 2ddb09ef34e39d4e0e945f6a54587d776636cdfc Mon Sep 17 00:00:00 2001 From: xrendan Date: Thu, 21 May 2026 14:30:20 -0400 Subject: [PATCH] cms: 308-redirect old slugs to canonical URL on detail pages york_factory now supports editable slugs (PR #43) with friendly_id's :history module preserved, so old slugs continue to resolve to the current canonical record. Compare the requested slug against the canonical slug returned by the API and permanentRedirect to the new URL when they differ, on memos, toronto memos, posts, and builders. Point alternates.canonical at the canonical slug in every case (and add it to posts, where it was missing) so crawlers index the new URL. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/app/builders/[slug]/page.tsx | 8 ++++++-- src/app/memos/[slug]/page.tsx | 8 ++++++-- src/app/posts/[slug]/page.tsx | 7 ++++++- src/app/toronto/memos/[slug]/page.tsx | 8 ++++++-- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/app/builders/[slug]/page.tsx b/src/app/builders/[slug]/page.tsx index 97edea5..43600d3 100644 --- a/src/app/builders/[slug]/page.tsx +++ b/src/app/builders/[slug]/page.tsx @@ -1,4 +1,4 @@ -import { notFound } from "next/navigation"; +import { notFound, permanentRedirect } from "next/navigation"; import type { Metadata } from "next"; import Image from "next/image"; import Link from "next/link"; @@ -15,7 +15,7 @@ export async function generateMetadata({ return { title: `${builder.name}: ${builder.tagline}`, description: builder.quote ?? undefined, - alternates: { canonical: `/builders/${slug}` }, + alternates: { canonical: `/builders/${builder.slug}` }, openGraph: { title: `${builder.name}: ${builder.tagline} | Build Canada`, description: builder.quote ?? undefined, @@ -41,6 +41,10 @@ export default async function BuilderPage({ notFound(); } + if (builder.slug !== slug) { + permanentRedirect(`/builders/${builder.slug}`); + } + return (
diff --git a/src/app/memos/[slug]/page.tsx b/src/app/memos/[slug]/page.tsx index de905a7..9f15458 100644 --- a/src/app/memos/[slug]/page.tsx +++ b/src/app/memos/[slug]/page.tsx @@ -1,4 +1,4 @@ -import { notFound } from "next/navigation"; +import { notFound, permanentRedirect } from "next/navigation"; import type { Metadata } from "next"; import Image from "next/image"; import { fetchMemo, fetchMemos, getSiteConfig } from "@/lib/api"; @@ -42,7 +42,7 @@ export async function generateMetadata({ return { title, description, - alternates: { canonical: `/memos/${slug}` }, + alternates: { canonical: `/memos/${memo.slug}` }, openGraph: { title, description, @@ -73,6 +73,10 @@ export default async function MemoDetailPage({ notFound(); } + if (memo.slug !== slug) { + permanentRedirect(`/memos/${memo.slug}`); + } + const authorImage = memo.author.name === "Build Canada" ? "/assets/logos/buildcanada-logo-square.svg" diff --git a/src/app/posts/[slug]/page.tsx b/src/app/posts/[slug]/page.tsx index 583b27f..1b472a4 100644 --- a/src/app/posts/[slug]/page.tsx +++ b/src/app/posts/[slug]/page.tsx @@ -1,4 +1,4 @@ -import { notFound } from "next/navigation"; +import { notFound, permanentRedirect } from "next/navigation"; import type { Metadata } from "next"; import Link from "next/link"; import { fetchPost, fetchPosts, getSiteConfig } from "@/lib/api"; @@ -39,6 +39,7 @@ export async function generateMetadata({ return { title, description, + alternates: { canonical: `/posts/${post.slug}` }, openGraph: { title, description, @@ -68,6 +69,10 @@ export default async function PostDetailPage({ notFound(); } + if (post.slug !== slug) { + permanentRedirect(`/posts/${post.slug}`); + } + const date = new Date(post.publishedAt || post.createdAt).toLocaleDateString( "en-CA", { year: "numeric", month: "long", day: "numeric" }, diff --git a/src/app/toronto/memos/[slug]/page.tsx b/src/app/toronto/memos/[slug]/page.tsx index 615c90f..7b170ec 100644 --- a/src/app/toronto/memos/[slug]/page.tsx +++ b/src/app/toronto/memos/[slug]/page.tsx @@ -1,4 +1,4 @@ -import { notFound } from "next/navigation"; +import { notFound, permanentRedirect } from "next/navigation"; import type { Metadata } from "next"; import Image from "next/image"; import { fetchMemo, fetchMemos, getSiteConfig } from "@/lib/api"; @@ -48,7 +48,7 @@ export async function generateMetadata({ return { title, description, - alternates: { canonical: `${BASE_PATH}/${slug}` }, + alternates: { canonical: `${BASE_PATH}/${memo.slug}` }, openGraph: { title, description, @@ -79,6 +79,10 @@ export default async function TorontoMemoDetailPage({ notFound(); } + if (memo.slug !== slug) { + permanentRedirect(`${BASE_PATH}/${memo.slug}`); + } + const authorImage = memo.author.photo; const keyMessages = memo.keyMessages;