Give every public route its own description, and keep canonicals and structured data on the host that serves the page.

Decorative images on the marketing pages stay quiet, and every dialog close button is named Close.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
adilalloandCursor committed 2026-11-04 11:04:52 -07:00
1 parent 0b6e8b00dd
commit c180e79c38
52 files changed
+363 -209

No files matched your search

@@ -6,6 +6,8 @@ import {
type LegalDocumentMessages,
type LegalDocumentSlug,
} from "../../../lib/legalSyntheticPost";
import { getRequestSiteOrigin } from "../../../lib/requestSiteOrigin";
import { absoluteSiteUrl } from "../../../lib/siteMetadata";
import ContentBanner from "../../components/sections/ContentBanner";
type LegalDocumentPageProps = {
@@ -97,11 +99,12 @@ export function legalPageMetadata(
* articles, Ask Organizer, and decorative shapes.
* https://www.figma.com/design/agv0VBLiBlcnSAaiAORgPR/Community-Rule-System?node-id=19003-23305
*/
export default function LegalDocumentPage({
export default async function LegalDocumentPage({
slug,
path,
page,
}: LegalDocumentPageProps) {
const origin = await getRequestSiteOrigin();
const post = buildLegalSyntheticPost(slug, page);
const structuredData = {
@@ -109,12 +112,12 @@ export default function LegalDocumentPage({
"@type": "WebPage",
name: page.banner.title,
description: page.banner.description,
url: `https://communityrule.com${path}`,
url: absoluteSiteUrl(path, origin),
dateModified: page.banner.date,
publisher: {
"@type": "Organization",
name: "CommunityRule",
url: "https://communityrule.com",
url: absoluteSiteUrl("/", origin),
},
};
@@ -126,13 +129,13 @@ export default function LegalDocumentPage({
"@type": "ListItem",
position: 1,
name: "Home",
item: "https://communityrule.com",
item: absoluteSiteUrl("/", origin),
},
{
"@type": "ListItem",
position: 2,
name: page.banner.title,
item: `https://communityrule.com${path}`,
item: absoluteSiteUrl(path, origin),
},
],
};
+11 -7
View File
@@ -8,7 +8,9 @@ import {
getRelatedBlogPosts,
} from "../../../../lib/content";
import { logger } from "../../../../lib/logger";
import { getRequestSiteOrigin } from "../../../../lib/requestSiteOrigin";
import {
absoluteSiteUrl,
articleShareImages,
routeMetadata,
} from "../../../../lib/siteMetadata";
@@ -130,6 +132,8 @@ export default async function BlogPostPage({ params }: PageProps) {
3,
);
const origin = await getRequestSiteOrigin();
// Generate structured data for search engines
const structuredData = {
"@context": "https://schema.org",
@@ -143,19 +147,19 @@ export default async function BlogPostPage({ params }: PageProps) {
publisher: {
"@type": "Organization",
name: "CommunityRule",
url: "https://communityrule.com",
url: absoluteSiteUrl("/", origin),
logo: {
"@type": "ImageObject",
url: "https://communityrule.com/assets/logos/community-rule.svg",
url: absoluteSiteUrl("/assets/logos/community-rule.svg", origin),
},
},
datePublished: post.frontmatter.date,
dateModified: post.frontmatter.date,
mainEntityOfPage: {
"@type": "WebPage",
"@id": `https://communityrule.com/blog/${post.slug}`,
"@id": absoluteSiteUrl(`/blog/${post.slug}`, origin),
},
url: `https://communityrule.com/blog/${post.slug}`,
url: absoluteSiteUrl(`/blog/${post.slug}`, origin),
articleSection: "Community Building",
keywords: ["community", "governance", "decision making", "collaboration"],
};
@@ -169,19 +173,19 @@ export default async function BlogPostPage({ params }: PageProps) {
"@type": "ListItem",
position: 1,
name: "Home",
item: "https://communityrule.com",
item: absoluteSiteUrl("/", origin),
},
{
"@type": "ListItem",
position: 2,
name: "Blog",
item: "https://communityrule.com/blog",
item: absoluteSiteUrl("/blog", origin),
},
{
"@type": "ListItem",
position: 3,
name: post.frontmatter.title,
item: `https://communityrule.com/blog/${post.slug}`,
item: absoluteSiteUrl(`/blog/${post.slug}`, origin),
},
],
};
+10 -8
View File
@@ -10,7 +10,8 @@ import {
buildHowItWorksSyntheticPost,
HOW_IT_WORKS_SENTINEL_SLUG,
} from "../../../lib/howItWorksSyntheticPost";
import { routeMetadata } from "../../../lib/siteMetadata";
import { getRequestSiteOrigin } from "../../../lib/requestSiteOrigin";
import { absoluteSiteUrl, routeMetadata } from "../../../lib/siteMetadata";
import ContentBanner from "../../components/sections/ContentBanner";
import ContentTemplateDecorativeShapes from "../_components/ContentTemplateDecorativeShapes";
import AskOrganizer from "../../components/sections/AskOrganizer";
@@ -36,14 +37,15 @@ export async function generateMetadata(): Promise<Metadata> {
keywords: meta.keywords,
openGraph: {
title: page.banner.title,
description: page.banner.description,
description: meta.description,
type: "website",
siteName: messages.metadata.siteName,
},
});
}
export default function HowItWorksPage() {
export default async function HowItWorksPage() {
const origin = await getRequestSiteOrigin();
const page = messages.pages.howItWorks;
const syntheticPost = buildHowItWorksSyntheticPost(page);
@@ -61,12 +63,12 @@ export default function HowItWorksPage() {
"@context": "https://schema.org",
"@type": "WebPage",
name: page.banner.title,
description: page.banner.description,
url: "https://communityrule.com/how-it-works",
description: messages.metadata.howItWorks.description,
url: absoluteSiteUrl("/how-it-works", origin),
publisher: {
"@type": "Organization",
name: "CommunityRule",
url: "https://communityrule.com",
url: absoluteSiteUrl("/", origin),
},
};
@@ -78,13 +80,13 @@ export default function HowItWorksPage() {
"@type": "ListItem",
position: 1,
name: "Home",
item: "https://communityrule.com",
item: absoluteSiteUrl("/", origin),
},
{
"@type": "ListItem",
position: 2,
name: page.banner.title,
item: "https://communityrule.com/how-it-works",
item: absoluteSiteUrl("/how-it-works", origin),
},
],
};
+8 -12
View File
@@ -1,22 +1,12 @@
import dynamic from "next/dynamic";
import { Suspense, type ReactNode } from "react";
import ConditionalNavigation from "../components/navigation/ConditionalNavigation";
import FooterWithOrigin from "../components/navigation/FooterWithOrigin";
import SkipToContent from "../components/navigation/SkipToContent";
import { MessagesProvider } from "../contexts/MessagesContext";
import { AuthModalProvider } from "../contexts/AuthModalContext";
import { MAIN_CONTENT_ID } from "../../lib/mainContent";
import marketingMessages from "../../messages/en/marketing";
// Site footer is part of the public marketing chrome only — not rendered for
// signed-in product surfaces, admin dashboards, or dev previews. See
// `.cursor/rules/routes.mdc` for the full chrome composition map.
const Footer = dynamic(() => import("../components/navigation/Footer"), {
loading: () => (
<footer className="bg-[var(--color-surface-default-primary)] w-full min-h-[200px]" />
),
ssr: true,
});
export default function MarketingLayout({ children }: { children: ReactNode }) {
return (
<MessagesProvider messages={marketingMessages}>
@@ -37,7 +27,13 @@ export default function MarketingLayout({ children }: { children: ReactNode }) {
>
{children}
</main>
<Footer />
<Suspense
fallback={
<footer className="min-h-[200px] w-full bg-[var(--color-surface-default-primary)]" />
}
>
<FooterWithOrigin />
</Suspense>
</AuthModalProvider>
</MessagesProvider>
);
+12 -1
View File
@@ -8,7 +8,18 @@ import HeroBanner from "../components/sections/HeroBanner";
import AskOrganizer from "../components/sections/AskOrganizer";
import { MarketingRuleStackSection } from "./_components/MarketingRuleStackSection";
export const metadata: Metadata = routeMetadata("/");
export const metadata: Metadata = routeMetadata("/", {
title: { absolute: messages.metadata.home.title },
description: messages.metadata.home.description,
openGraph: {
title: messages.metadata.home.title,
description: messages.metadata.home.description,
},
twitter: {
title: messages.metadata.home.title,
description: messages.metadata.home.description,
},
});
// Code split below-the-fold components to reduce initial bundle size
const LogoWall = dynamic(() => import("../components/sections/LogoWall"), {
+1
View File
@@ -10,6 +10,7 @@ import TemplatesPageClient from "./TemplatesPageClient";
export const metadata: Metadata = routeMetadata("/templates", {
title: messages.metadata.templates.title,
description: messages.metadata.templates.description,
openGraph: {
images: templatesShareImages(),
},
+8 -6
View File
@@ -12,7 +12,8 @@ import {
USE_CASE_DETAIL_SLUGS,
useCaseContentKeyForSlug,
} from "../../../../lib/useCaseSyntheticPost";
import { routeMetadata } from "../../../../lib/siteMetadata";
import { getRequestSiteOrigin } from "../../../../lib/requestSiteOrigin";
import { absoluteSiteUrl, routeMetadata } from "../../../../lib/siteMetadata";
import ContentBanner from "../../../components/sections/ContentBanner";
import AskOrganizer from "../../../components/sections/AskOrganizer";
import type { AskOrganizerVariant } from "../../../components/sections/AskOrganizer/AskOrganizer.types";
@@ -56,6 +57,7 @@ export default async function UseCaseDetailPage({ params }: PageProps) {
notFound();
}
const origin = await getRequestSiteOrigin();
const detail = messages.pages.useCasesDetail;
const entry = getUseCaseDetailEntry(slug, detail);
const syntheticPost = buildUseCaseSyntheticPost(slug, detail);
@@ -69,11 +71,11 @@ export default async function UseCaseDetailPage({ params }: PageProps) {
"@type": "WebPage",
name: entry.banner.title,
description: entry.banner.description,
url: `https://communityrule.com/use-cases/${slug}`,
url: absoluteSiteUrl(`/use-cases/${slug}`, origin),
publisher: {
"@type": "Organization",
name: "CommunityRule",
url: "https://communityrule.com",
url: absoluteSiteUrl("/", origin),
},
};
@@ -85,19 +87,19 @@ export default async function UseCaseDetailPage({ params }: PageProps) {
"@type": "ListItem",
position: 1,
name: "Home",
item: "https://communityrule.com",
item: absoluteSiteUrl("/", origin),
},
{
"@type": "ListItem",
position: 2,
name: "Learn",
item: "https://communityrule.com/learn",
item: absoluteSiteUrl("/learn", origin),
},
{
"@type": "ListItem",
position: 3,
name: entry.banner.title,
item: `https://communityrule.com/use-cases/${slug}`,
item: absoluteSiteUrl(`/use-cases/${slug}`, origin),
},
],
};