diff --git a/apps/web/__tests__/unit/canonical-urls.test.ts b/apps/web/__tests__/unit/canonical-urls.test.ts index ddd55e98169..ec879541611 100644 --- a/apps/web/__tests__/unit/canonical-urls.test.ts +++ b/apps/web/__tests__/unit/canonical-urls.test.ts @@ -113,6 +113,10 @@ const expectedCanonicals: Array<{ file: string; canonical: string }> = [ file: "(site)/(seo)/google-chrome-screen-recorder/page.tsx", canonical: "https://cap.so/google-chrome-screen-recorder", }, + { + file: "(site)/deactivate-license/page.tsx", + canonical: "https://cap.so/deactivate-license", + }, ]; describe("Canonical URLs", () => { @@ -136,4 +140,19 @@ describe("Canonical URLs", () => { `canonical: \`https://cap.so/tools/convert/\${conversionPath}\``, ); }); + + it("dynamic blog [slug] route generates canonical from the post slug", () => { + const content = readPage("(site)/blog/[slug]/page.tsx"); + expect(content).toContain( + `canonical: \`https://cap.so/blog/\${post.slug}\``, + ); + }); + + it("dynamic docs route generates canonical from the slug, with /docs as the docs home", () => { + const content = readPage("(docs)/docs/[[...slug]]/page.tsx"); + expect(content).toContain( + `const docPath = isDocsHome ? "/docs" : \`/docs/\${slug}\`;`, + ); + expect(content).toContain(`canonical: \`https://cap.so\${docPath}\``); + }); }); diff --git a/apps/web/app/(docs)/docs/[[...slug]]/page.tsx b/apps/web/app/(docs)/docs/[[...slug]]/page.tsx index c7a642589ca..28dc276a968 100644 --- a/apps/web/app/(docs)/docs/[[...slug]]/page.tsx +++ b/apps/web/app/(docs)/docs/[[...slug]]/page.tsx @@ -28,6 +28,7 @@ export async function generateMetadata( // meaningless share card — present the landing page as the docs home. const isDocsHome = slug === "introduction"; const pageTitle = isDocsHome ? "Cap Documentation" : `${title} - Cap Docs`; + const docPath = isDocsHome ? "/docs" : `/docs/${slug}`; const description = summary || title; const ogImage = image ? `${buildEnv.NEXT_PUBLIC_WEB_URL}${image}` @@ -42,11 +43,12 @@ export async function generateMetadata( return { title: pageTitle, description, + alternates: { canonical: `https://cap.so${docPath}` }, openGraph: { title: pageTitle, description, type: "article", - url: `${buildEnv.NEXT_PUBLIC_WEB_URL}/docs/${slug}`, + url: `${buildEnv.NEXT_PUBLIC_WEB_URL}${docPath}`, images: [{ url: ogImage, width: 1200, height: 630, alt: pageTitle }], }, twitter: { diff --git a/apps/web/app/(site)/blog/[slug]/page.tsx b/apps/web/app/(site)/blog/[slug]/page.tsx index 6d9f0281167..270d9609a6e 100644 --- a/apps/web/app/(site)/blog/[slug]/page.tsx +++ b/apps/web/app/(site)/blog/[slug]/page.tsx @@ -57,6 +57,7 @@ export async function generateMetadata( return { title, description, + alternates: { canonical: `https://cap.so/blog/${post.slug}` }, openGraph: { title, description, diff --git a/apps/web/app/(site)/deactivate-license/page.tsx b/apps/web/app/(site)/deactivate-license/page.tsx index 024f3046a94..3ed2841f756 100644 --- a/apps/web/app/(site)/deactivate-license/page.tsx +++ b/apps/web/app/(site)/deactivate-license/page.tsx @@ -5,6 +5,7 @@ export const metadata: Metadata = { title: "Deactivate License — Cap", description: "Deactivate your Cap commercial license from its current device to use it elsewhere.", + alternates: { canonical: "https://cap.so/deactivate-license" }, }; export default function App() {