diff --git a/apps/website/e2e/website.spec.ts b/apps/website/e2e/website.spec.ts index 8e4bb59d7..affefc2bf 100644 --- a/apps/website/e2e/website.spec.ts +++ b/apps/website/e2e/website.spec.ts @@ -225,6 +225,16 @@ test('docs pages render canonical and social metadata', async ({ page }) => { ); }); +test('marketing pages render canonical and page-specific social URLs', async ({ page }) => { + for (const route of ['/', '/angular', '/render', '/chat', '/pricing', '/contact', '/pilot-to-prod', '/solutions']) { + await page.goto(route); + const expectedUrl = route === '/' ? 'https://threadplane.ai' : `https://threadplane.ai${route}`; + + await expect(page.locator('link[rel="canonical"]'), `${route} canonical`).toHaveAttribute('href', expectedUrl); + await expect(page.locator('meta[property="og:url"]'), `${route} og:url`).toHaveAttribute('content', expectedUrl); + } +}); + test('representative docs pages do not create page-level horizontal overflow', async ({ page }) => { const routes = [ '/docs', diff --git a/apps/website/src/app/angular/page.tsx b/apps/website/src/app/angular/page.tsx index eee32c43b..139cc048a 100644 --- a/apps/website/src/app/angular/page.tsx +++ b/apps/website/src/app/angular/page.tsx @@ -9,11 +9,14 @@ import { FeatureBlock } from '../../components/landing/FeatureBlock'; import { WhitePaperBlock } from '../../components/landing/WhitePaperBlock'; import { FinalCTA } from '../../components/landing/FinalCTA'; import { AngularCodeShowcase } from '../../components/landing/angular/AngularCodeShowcase'; +import { createPageMetadata } from '../../lib/site-metadata'; -export const metadata = { +export const metadata = createPageMetadata({ title: '@ngaf/langgraph — Agent Streaming for Angular', description: 'Ship LangGraph agents in Angular. Signal-native streaming, thread persistence, interrupts, and deterministic testing.', -}; + pathname: '/angular', + type: 'website', +}); export default async function AngularPage() { return ( diff --git a/apps/website/src/app/chat/page.tsx b/apps/website/src/app/chat/page.tsx index 6512f544a..11c0ff847 100644 --- a/apps/website/src/app/chat/page.tsx +++ b/apps/website/src/app/chat/page.tsx @@ -9,11 +9,14 @@ import { FeatureBlock } from '../../components/landing/FeatureBlock'; import { WhitePaperBlock } from '../../components/landing/WhitePaperBlock'; import { FinalCTA } from '../../components/landing/FinalCTA'; import { ChatLandingCodeShowcase } from '../../components/landing/chat-landing/ChatLandingCodeShowcase'; +import { createPageMetadata } from '../../lib/site-metadata'; -export const metadata = { +export const metadata = createPageMetadata({ title: '@ngaf/chat — Batteries-Included Agent Chat for Angular', description: 'Production agent chat UI in days, not sprints. Built on Vercel json-render and Google A2UI specs.', -}; + pathname: '/chat', + type: 'website', +}); export default async function ChatPage() { return ( diff --git a/apps/website/src/app/contact/page.tsx b/apps/website/src/app/contact/page.tsx index f50eaec85..dc8e6dcc2 100644 --- a/apps/website/src/app/contact/page.tsx +++ b/apps/website/src/app/contact/page.tsx @@ -1,5 +1,4 @@ // SPDX-License-Identifier: MIT -import type { Metadata } from 'next'; import React, { Suspense } from 'react'; import { tokens } from '@ngaf/design-tokens'; import { Container } from '../../components/ui/Container'; @@ -9,17 +8,14 @@ import { ContactForm } from '../../components/contact/ContactForm'; import { GitHubStarsPill } from '../../components/contact/GitHubStarsPill'; import { SlaCard } from '../../components/contact/SlaCard'; import { AltChannelRow } from '../../components/contact/AltChannelRow'; +import { createPageMetadata } from '../../lib/site-metadata'; -export const metadata: Metadata = { +export const metadata = createPageMetadata({ title: 'Talk to an engineer — ThreadPlane', - description: - "Tell us what you're shipping. We'll reply within one business day — usually with code, not a calendar invite.", - openGraph: { - title: 'Talk to an engineer — ThreadPlane', - description: "Tell us what you're shipping. We'll reply within one business day.", - type: 'website', - }, -}; + description: "Tell us what you're shipping. We'll reply within one business day — usually with code, not a calendar invite.", + pathname: '/contact', + type: 'website', +}); export default function ContactPage() { return ( diff --git a/apps/website/src/app/page.tsx b/apps/website/src/app/page.tsx index 64288c248..4c5008e71 100644 --- a/apps/website/src/app/page.tsx +++ b/apps/website/src/app/page.tsx @@ -10,6 +10,14 @@ import { Promises } from '../components/landing/Promises'; import { HomeFAQ } from '../components/landing/HomeFAQ'; import { FinalCTA } from '../components/landing/FinalCTA'; import { tokens } from '@ngaf/design-tokens'; +import { createPageMetadata } from '../lib/site-metadata'; + +export const metadata = createPageMetadata({ + title: 'Agent UI for Angular — Signal-Native Streaming for Angular + LangGraph', + description: 'The enterprise Angular agent framework for LangChain. Signal-native streaming, thread persistence, and production patterns for Angular 20+.', + pathname: '/', + type: 'website', +}); export default async function HomePage() { return ( diff --git a/apps/website/src/app/pilot-to-prod/page.tsx b/apps/website/src/app/pilot-to-prod/page.tsx index 56700e0d7..9f959b152 100644 --- a/apps/website/src/app/pilot-to-prod/page.tsx +++ b/apps/website/src/app/pilot-to-prod/page.tsx @@ -10,11 +10,14 @@ import { BrowserFrame } from '../../components/ui/BrowserFrame'; import { WhitePaperBlock } from '../../components/landing/WhitePaperBlock'; import { Promises } from '../../components/landing/Promises'; import { FinalCTA } from '../../components/landing/FinalCTA'; +import { createPageMetadata } from '../../lib/site-metadata'; -export const metadata = { +export const metadata = createPageMetadata({ title: 'Pilot to Production — Agent UI for Angular', description: 'Close the last-mile gap. The 3-month pilot engagement is included with every app deployment license. We work alongside your Angular team to ship your first agent to production.', -}; + pathname: '/pilot-to-prod', + type: 'website', +}); export default function PilotToProdPage() { return ( diff --git a/apps/website/src/app/pricing/page.tsx b/apps/website/src/app/pricing/page.tsx index e26cccb8e..2cab8e800 100644 --- a/apps/website/src/app/pricing/page.tsx +++ b/apps/website/src/app/pricing/page.tsx @@ -7,11 +7,14 @@ import { CompareTable } from '../../components/pricing/CompareTable'; import { CompatibilityMatrix } from '../../components/pricing/CompatibilityMatrix'; import { LeadForm } from '../../components/pricing/LeadForm'; import { FinalCTA } from '../../components/landing/FinalCTA'; +import { createPageMetadata } from '../../lib/site-metadata'; -export const metadata = { +export const metadata = createPageMetadata({ title: 'Pricing — Agent UI for Angular', description: 'Simple, transparent pricing. MIT-licensed libraries are free forever. Enterprise contracts available.', -}; + pathname: '/pricing', + type: 'website', +}); export default function PricingPage() { return ( diff --git a/apps/website/src/app/render/page.tsx b/apps/website/src/app/render/page.tsx index e59e34338..43d94b507 100644 --- a/apps/website/src/app/render/page.tsx +++ b/apps/website/src/app/render/page.tsx @@ -9,11 +9,14 @@ import { FeatureBlock } from '../../components/landing/FeatureBlock'; import { WhitePaperBlock } from '../../components/landing/WhitePaperBlock'; import { FinalCTA } from '../../components/landing/FinalCTA'; import { RenderCodeShowcase } from '../../components/landing/render/RenderCodeShowcase'; +import { createPageMetadata } from '../../lib/site-metadata'; -export const metadata = { +export const metadata = createPageMetadata({ title: '@ngaf/render — Generative UI for Angular', description: 'Agents that render UI without coupling to your frontend. Built on Vercel json-render spec.', -}; + pathname: '/render', + type: 'website', +}); export default async function RenderPage() { return ( diff --git a/apps/website/src/app/solutions/page.tsx b/apps/website/src/app/solutions/page.tsx index fa71b130d..3f8e1347b 100644 --- a/apps/website/src/app/solutions/page.tsx +++ b/apps/website/src/app/solutions/page.tsx @@ -8,11 +8,14 @@ import { Pill } from '../../components/ui/Pill'; import { WhitePaperBlock } from '../../components/landing/WhitePaperBlock'; import { FinalCTA } from '../../components/landing/FinalCTA'; import { SOLUTIONS } from '../../lib/solutions-data'; +import { createPageMetadata } from '../../lib/site-metadata'; -export const metadata = { +export const metadata = createPageMetadata({ title: 'Solutions — Agent UI for Angular', description: 'See how Agent UI for Angular solves enterprise challenges — compliance, analytics, and customer support.', -}; + pathname: '/solutions', + type: 'website', +}); export default function SolutionsIndexPage() { return (