Give each route its own title and canonical, and add robots.txt plus a sitemap.
The root layout was stamping the homepage title and production root onto every page. Routes now supply a page segment through one em-dash template, canonicals follow the request path, and non-production hosts disallow crawlers. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,8 +1,14 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Suspense, type ReactNode } from "react";
|
||||
import ConditionalNavigation from "../components/navigation/ConditionalNavigation";
|
||||
import { MessagesProvider } from "../contexts/MessagesContext";
|
||||
import { AuthModalProvider } from "../contexts/AuthModalContext";
|
||||
import messages from "../../messages/en/index";
|
||||
import { NO_INDEX_ROBOTS } from "../../lib/siteMetadata";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
robots: NO_INDEX_ROBOTS,
|
||||
};
|
||||
|
||||
// `force-dynamic` removed in favor of `experimental.cacheComponents` (Next 16).
|
||||
// See `(app)/layout.tsx` for the matching `<Suspense fallback={null}>` rationale
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import type { Metadata } from "next";
|
||||
import messages from "../../../messages/en/index";
|
||||
import { routeMetadata } from "../../../lib/siteMetadata";
|
||||
import MonitorPageContent from "./MonitorPageContent";
|
||||
|
||||
export const metadata: Metadata = routeMetadata("/monitor", {
|
||||
title: messages.metadata.monitor.title,
|
||||
});
|
||||
|
||||
export default function MonitorPage() {
|
||||
return <MonitorPageContent />;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { CreateFlowScreenView } from "../screens/CreateFlowScreenView";
|
||||
import { createFlowStepPath, CREATE_ROUTES } from "../utils/createFlowPaths";
|
||||
import { isValidStep } from "../utils/flowSteps";
|
||||
import type { CreateFlowStep } from "../types";
|
||||
import { routeMetadata } from "../../../../lib/siteMetadata";
|
||||
|
||||
/**
|
||||
* Single dynamic route for the whole create wizard (every step in `FLOW_STEP_ORDER`).
|
||||
@@ -14,6 +17,14 @@ interface PageProps {
|
||||
params: Promise<{ screenId: string }>;
|
||||
}
|
||||
|
||||
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
|
||||
const { screenId: raw } = await params;
|
||||
const path = isValidStep(raw)
|
||||
? createFlowStepPath(raw)
|
||||
: `${CREATE_ROUTES.createRoot}/${raw}`;
|
||||
return routeMetadata(path);
|
||||
}
|
||||
|
||||
export default async function CreateFlowScreenPage({ params }: PageProps) {
|
||||
const { screenId: raw } = await params;
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import type { Metadata } from "next";
|
||||
import type { ReactNode } from "react";
|
||||
import CreateFlowLayoutGate from "./CreateFlowLayoutGate";
|
||||
import messages from "../../../messages/en/index";
|
||||
import { routeMetadata } from "../../../lib/siteMetadata";
|
||||
|
||||
export const metadata: Metadata = routeMetadata("/create", {
|
||||
title: messages.metadata.create.title,
|
||||
});
|
||||
|
||||
export default function CreateFlowLayout({ children }: { children: ReactNode }) {
|
||||
return <CreateFlowLayoutGate>{children}</CreateFlowLayoutGate>;
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Suspense, type ReactNode } from "react";
|
||||
import ConditionalNavigation from "../components/navigation/ConditionalNavigation";
|
||||
import { MessagesProvider } from "../contexts/MessagesContext";
|
||||
import { AuthModalProvider } from "../contexts/AuthModalContext";
|
||||
import messages from "../../messages/en/index";
|
||||
import { NO_INDEX_ROBOTS } from "../../lib/siteMetadata";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
robots: NO_INDEX_ROBOTS,
|
||||
};
|
||||
|
||||
// `force-dynamic` removed in favor of `experimental.cacheComponents` (Next 16).
|
||||
// `ConditionalNavigation` reads `cr_session` server-side (and `usePathname()`
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import type { Metadata } from "next";
|
||||
import messages from "../../../messages/en/index";
|
||||
import { NO_INDEX_ROBOTS, routeMetadata } from "../../../lib/siteMetadata";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Log in · CommunityRule",
|
||||
robots: { index: false, follow: false },
|
||||
};
|
||||
export const metadata: Metadata = routeMetadata("/login", {
|
||||
title: messages.metadata.login.title,
|
||||
robots: NO_INDEX_ROBOTS,
|
||||
});
|
||||
|
||||
export default function LoginLayout({
|
||||
children,
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import type { Metadata } from "next";
|
||||
import messages from "../../../messages/en/index";
|
||||
import { NO_INDEX_ROBOTS, routeMetadata } from "../../../lib/siteMetadata";
|
||||
import ProfilePageClient from "./ProfilePageClient";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Profile · CommunityRule",
|
||||
robots: { index: false, follow: false },
|
||||
};
|
||||
export const metadata: Metadata = routeMetadata("/profile", {
|
||||
title: messages.metadata.profile.title,
|
||||
robots: NO_INDEX_ROBOTS,
|
||||
});
|
||||
|
||||
export default function ProfilePage() {
|
||||
return <ProfilePageClient />;
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import type { ReactNode } from "react";
|
||||
import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { MessagesProvider } from "../contexts/MessagesContext";
|
||||
import { AuthModalProvider } from "../contexts/AuthModalContext";
|
||||
import messages from "../../messages/en/index";
|
||||
import { NO_INDEX_ROBOTS } from "../../lib/siteMetadata";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
robots: NO_INDEX_ROBOTS,
|
||||
};
|
||||
|
||||
// Development-only previews (e.g. `/components-preview`) — no public chrome.
|
||||
export default function DevLayout({ children }: { children: ReactNode }) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Metadata } from "next";
|
||||
import messages from "../../../messages/en/index";
|
||||
import {
|
||||
ASSETS,
|
||||
@@ -6,6 +7,7 @@ import {
|
||||
structureBeforeCrisisPath,
|
||||
} from "../../../lib/assetUtils";
|
||||
import { getTranslation } from "../../../lib/i18n/getTranslation";
|
||||
import { routeMetadata } from "../../../lib/siteMetadata";
|
||||
import AboutHeader from "../../components/type/AboutHeader";
|
||||
import type { AboutHeaderSegment } from "../../components/type/AboutHeader";
|
||||
import Stats from "../../components/sections/Stats";
|
||||
@@ -18,6 +20,10 @@ import type { FaqAccordionItem } from "../../components/sections/Accordion";
|
||||
import QuoteBlock from "../../components/sections/QuoteBlock";
|
||||
import AskOrganizer from "../../components/sections/AskOrganizer";
|
||||
|
||||
export const metadata: Metadata = routeMetadata("/about", {
|
||||
title: messages.metadata.about.title,
|
||||
});
|
||||
|
||||
function asArray<T>(value: unknown): T[] {
|
||||
return Array.isArray(value) ? value : [];
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
getRelatedBlogPosts,
|
||||
} from "../../../../lib/content";
|
||||
import { logger } from "../../../../lib/logger";
|
||||
import { routeMetadata } from "../../../../lib/siteMetadata";
|
||||
import messages from "../../../../messages/en/index";
|
||||
import ContentBanner from "../../../components/sections/ContentBanner";
|
||||
import AskOrganizer from "../../../components/sections/AskOrganizer";
|
||||
import ContentTemplateDecorativeShapes from "../../_components/ContentTemplateDecorativeShapes";
|
||||
@@ -62,13 +64,13 @@ export async function generateMetadata({
|
||||
const post = getBlogPostBySlug(slug);
|
||||
|
||||
if (!post) {
|
||||
return {
|
||||
title: "Post Not Found",
|
||||
description: "The requested blog post could not be found.",
|
||||
};
|
||||
return routeMetadata(`/blog/${slug}`, {
|
||||
title: messages.metadata.blog.notFoundTitle,
|
||||
description: messages.metadata.blog.fallbackDescription,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
return routeMetadata(`/blog/${slug}`, {
|
||||
title: post.frontmatter.title,
|
||||
description: post.frontmatter.description,
|
||||
authors: [{ name: post.frontmatter.author }],
|
||||
@@ -78,8 +80,8 @@ export async function generateMetadata({
|
||||
type: "article",
|
||||
publishedTime: post.frontmatter.date,
|
||||
authors: [post.frontmatter.author],
|
||||
url: `https://communityrule.com/blog/${slug}`,
|
||||
siteName: "CommunityRule",
|
||||
url: `/blog/${slug}`,
|
||||
siteName: messages.metadata.siteName,
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
@@ -87,12 +89,12 @@ export async function generateMetadata({
|
||||
description: post.frontmatter.description,
|
||||
creator: "@communityrule",
|
||||
},
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error("Error generating metadata:", error);
|
||||
return {
|
||||
title: "Blog Post",
|
||||
description: "A blog post from our community.",
|
||||
title: messages.metadata.blog.fallbackTitle,
|
||||
description: messages.metadata.blog.fallbackDescription,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
import { getAllBlogPosts } from "../../../lib/content";
|
||||
import ContentThumbnailTemplate from "../../components/content/ContentThumbnailTemplate";
|
||||
import type { Metadata } from "next";
|
||||
import messages from "../../../messages/en/index";
|
||||
import { routeMetadata } from "../../../lib/siteMetadata";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Blog - CommunityRule",
|
||||
description:
|
||||
"Learn about community governance, decision-making, and building successful organizations.",
|
||||
const blogMeta = messages.metadata.blog;
|
||||
|
||||
export const metadata: Metadata = routeMetadata("/blog", {
|
||||
title: blogMeta.title,
|
||||
description: blogMeta.description,
|
||||
openGraph: {
|
||||
title: "Blog - CommunityRule",
|
||||
description:
|
||||
"Learn about community governance, decision-making, and building successful organizations.",
|
||||
url: "https://communityrule.com/blog",
|
||||
siteName: "CommunityRule",
|
||||
title: blogMeta.title,
|
||||
description: blogMeta.description,
|
||||
url: "/blog",
|
||||
siteName: messages.metadata.siteName,
|
||||
type: "website",
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: "Blog - CommunityRule",
|
||||
description:
|
||||
"Learn about community governance, decision-making, and building successful organizations.",
|
||||
title: blogMeta.title,
|
||||
description: blogMeta.description,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export default function BlogPage() {
|
||||
const posts = getAllBlogPosts();
|
||||
@@ -34,11 +35,10 @@ export default function BlogPage() {
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-[var(--color-content-default-primary)] mb-4">
|
||||
Blog
|
||||
{blogMeta.title}
|
||||
</h1>
|
||||
<p className="text-lg text-[var(--color-content-default-secondary)] max-w-2xl mx-auto">
|
||||
Learn about community governance, decision-making, and building
|
||||
successful organizations.
|
||||
{blogMeta.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import type { Metadata } from "next";
|
||||
import messages from "../../../messages/en/index";
|
||||
import { legalPathForSlug } from "../../../lib/legalSyntheticPost";
|
||||
import { routeMetadata } from "../../../lib/siteMetadata";
|
||||
import LegalDocumentPage, {
|
||||
legalPageMetadata,
|
||||
} from "../_components/LegalDocumentPage";
|
||||
@@ -13,7 +14,10 @@ const SLUG = "cookies" as const;
|
||||
const PATH = legalPathForSlug(SLUG);
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
return legalPageMetadata(messages.metadata.cookies, messages.pages.cookies);
|
||||
return routeMetadata(
|
||||
PATH,
|
||||
legalPageMetadata(messages.metadata.cookies, messages.pages.cookies),
|
||||
);
|
||||
}
|
||||
|
||||
export default function CookiesPage() {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
buildHowItWorksSyntheticPost,
|
||||
HOW_IT_WORKS_SENTINEL_SLUG,
|
||||
} from "../../../lib/howItWorksSyntheticPost";
|
||||
import { routeMetadata } from "../../../lib/siteMetadata";
|
||||
import ContentBanner from "../../components/sections/ContentBanner";
|
||||
import ContentTemplateDecorativeShapes from "../_components/ContentTemplateDecorativeShapes";
|
||||
import AskOrganizer from "../../components/sections/AskOrganizer";
|
||||
@@ -29,7 +30,7 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
const meta = messages.metadata.howItWorks;
|
||||
const page = messages.pages.howItWorks;
|
||||
|
||||
return {
|
||||
return routeMetadata("/how-it-works", {
|
||||
title: meta.title,
|
||||
description: meta.description,
|
||||
keywords: meta.keywords,
|
||||
@@ -37,9 +38,9 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
title: page.banner.title,
|
||||
description: page.banner.description,
|
||||
type: "website",
|
||||
siteName: "CommunityRule",
|
||||
siteName: messages.metadata.siteName,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export default function HowItWorksPage() {
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import type { Metadata } from "next";
|
||||
import messages from "../../../messages/en/index";
|
||||
import { getTranslation } from "../../../lib/i18n/getTranslation";
|
||||
import { routeMetadata } from "../../../lib/siteMetadata";
|
||||
import ContentThumbnailTemplate from "../../components/content/ContentThumbnailTemplate";
|
||||
import ContentLockup from "../../components/type/ContentLockup";
|
||||
import AskOrganizer from "../../components/sections/AskOrganizer";
|
||||
import { getAllBlogPosts } from "../../../lib/content";
|
||||
|
||||
export const metadata: Metadata = routeMetadata("/learn", {
|
||||
title: messages.metadata.learn.title,
|
||||
});
|
||||
|
||||
export default function LearnPage() {
|
||||
const allPosts = getAllBlogPosts();
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import type { Metadata } from "next";
|
||||
import dynamic from "next/dynamic";
|
||||
import { Suspense } from "react";
|
||||
import messages from "../../messages/en/index";
|
||||
import { getTranslation } from "../../lib/i18n/getTranslation";
|
||||
import { routeMetadata } from "../../lib/siteMetadata";
|
||||
import HeroBanner from "../components/sections/HeroBanner";
|
||||
import AskOrganizer from "../components/sections/AskOrganizer";
|
||||
import { MarketingRuleStackSection } from "./_components/MarketingRuleStackSection";
|
||||
|
||||
export const metadata: Metadata = routeMetadata("/");
|
||||
|
||||
// Code split below-the-fold components to reduce initial bundle size
|
||||
const LogoWall = dynamic(() => import("../components/sections/LogoWall"), {
|
||||
loading: () => (
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import type { Metadata } from "next";
|
||||
import messages from "../../../messages/en/index";
|
||||
import { legalPathForSlug } from "../../../lib/legalSyntheticPost";
|
||||
import { routeMetadata } from "../../../lib/siteMetadata";
|
||||
import LegalDocumentPage, {
|
||||
legalPageMetadata,
|
||||
} from "../_components/LegalDocumentPage";
|
||||
@@ -13,7 +14,10 @@ const SLUG = "privacy" as const;
|
||||
const PATH = legalPathForSlug(SLUG);
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
return legalPageMetadata(messages.metadata.privacy, messages.pages.privacy);
|
||||
return routeMetadata(
|
||||
PATH,
|
||||
legalPageMetadata(messages.metadata.privacy, messages.pages.privacy),
|
||||
);
|
||||
}
|
||||
|
||||
export default function PrivacyPage() {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { notFound } from "next/navigation";
|
||||
import messages from "../../../../messages/en/index";
|
||||
import { getPublicPublishedRuleById } from "../../../../lib/server/publishedRules";
|
||||
import { parsePublishedDocumentForCommunityRuleDisplay } from "../../../../lib/create/publishedDocumentToDisplaySections";
|
||||
import { routeMetadata } from "../../../../lib/siteMetadata";
|
||||
import CommunityRule from "../../../components/type/CommunityRule";
|
||||
import HeaderLockup from "../../../components/type/HeaderLockup";
|
||||
|
||||
@@ -25,22 +26,22 @@ export async function generateMetadata({
|
||||
typeof rule.summary === "string" && rule.summary.trim().length > 0
|
||||
? rule.summary
|
||||
: undefined;
|
||||
return {
|
||||
return routeMetadata(`/rules/${rule.id}`, {
|
||||
title: rule.title,
|
||||
description,
|
||||
openGraph: {
|
||||
title: rule.title,
|
||||
description,
|
||||
type: "article",
|
||||
url: `https://communityrule.com/rules/${rule.id}`,
|
||||
siteName: "CommunityRule",
|
||||
url: `/rules/${rule.id}`,
|
||||
siteName: messages.metadata.siteName,
|
||||
},
|
||||
twitter: {
|
||||
card: "summary_large_image",
|
||||
title: rule.title,
|
||||
description,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export default async function PublicRuleDetailPage({ params }: PageProps) {
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import type { Metadata } from "next";
|
||||
import messages from "../../../messages/en/index";
|
||||
import { listRuleTemplatesFromDb } from "../../../lib/server/ruleTemplates";
|
||||
import { routeMetadata } from "../../../lib/siteMetadata";
|
||||
import { gridEntriesForFullCatalogWithFallback } from "../../../lib/templates/templateGridPresentation";
|
||||
import TemplatesPageClient from "./TemplatesPageClient";
|
||||
|
||||
export const metadata: Metadata = routeMetadata("/templates", {
|
||||
title: messages.metadata.templates.title,
|
||||
});
|
||||
|
||||
export default async function TemplatesPage() {
|
||||
const rows = await listRuleTemplatesFromDb();
|
||||
const initialGridEntries = gridEntriesForFullCatalogWithFallback(rows);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import type { Metadata } from "next";
|
||||
import messages from "../../../messages/en/index";
|
||||
import { legalPathForSlug } from "../../../lib/legalSyntheticPost";
|
||||
import { routeMetadata } from "../../../lib/siteMetadata";
|
||||
import LegalDocumentPage, {
|
||||
legalPageMetadata,
|
||||
} from "../_components/LegalDocumentPage";
|
||||
@@ -13,7 +14,10 @@ const SLUG = "terms" as const;
|
||||
const PATH = legalPathForSlug(SLUG);
|
||||
|
||||
export function generateMetadata(): Metadata {
|
||||
return legalPageMetadata(messages.metadata.terms, messages.pages.terms);
|
||||
return routeMetadata(
|
||||
PATH,
|
||||
legalPageMetadata(messages.metadata.terms, messages.pages.terms),
|
||||
);
|
||||
}
|
||||
|
||||
export default function TermsPage() {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
USE_CASE_DETAIL_SLUGS,
|
||||
useCaseContentKeyForSlug,
|
||||
} from "../../../../lib/useCaseSyntheticPost";
|
||||
import { 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";
|
||||
@@ -34,7 +35,7 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
||||
const contentKey = useCaseContentKeyForSlug(slug);
|
||||
const meta = messages.metadata.useCasesDetail[contentKey];
|
||||
|
||||
return {
|
||||
return routeMetadata(`/use-cases/${slug}`, {
|
||||
title: meta.title,
|
||||
description: meta.description,
|
||||
keywords: meta.keywords,
|
||||
@@ -42,9 +43,9 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
||||
title: meta.title,
|
||||
description: meta.description,
|
||||
type: "website",
|
||||
siteName: "CommunityRule",
|
||||
siteName: messages.metadata.siteName,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export default async function UseCaseDetailPage({ params }: PageProps) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Suspense } from "react";
|
||||
import messages from "../../../messages/en/index";
|
||||
import { CONTENT_CATALOG_SLUG_ORDER } from "../../../lib/assetUtils";
|
||||
import { getAllBlogPosts, getRelatedBlogPosts } from "../../../lib/content";
|
||||
import { routeMetadata } from "../../../lib/siteMetadata";
|
||||
import PageHeader from "../../components/type/PageHeader";
|
||||
import CaseStudy from "../../components/cards/CaseStudy";
|
||||
import UseCasesOrgs from "../../components/sections/UseCasesOrgs";
|
||||
@@ -60,7 +61,7 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
const description = messages.metadata.useCases.description;
|
||||
const keywords = messages.metadata.useCases.keywords;
|
||||
|
||||
return {
|
||||
return routeMetadata("/use-cases", {
|
||||
title,
|
||||
description,
|
||||
keywords,
|
||||
@@ -68,9 +69,9 @@ export async function generateMetadata(): Promise<Metadata> {
|
||||
title,
|
||||
description,
|
||||
type: "website",
|
||||
siteName: "CommunityRule",
|
||||
siteName: messages.metadata.siteName,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export default function UseCasesPage() {
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import messages from "../../../../../messages/en/index";
|
||||
import { resolveUseCaseCompletedRule } from "../../../../../lib/useCaseCompletedRule";
|
||||
import { routeMetadata } from "../../../../../lib/siteMetadata";
|
||||
import {
|
||||
USE_CASE_DETAIL_SLUGS,
|
||||
useCaseContentKeyForSlug,
|
||||
@@ -33,7 +34,7 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
||||
const contentKey = useCaseContentKeyForSlug(resolved.slug);
|
||||
const meta = messages.metadata.useCasesCompletedRule[contentKey];
|
||||
|
||||
return {
|
||||
return routeMetadata(`/use-cases/${resolved.slug}/rule`, {
|
||||
title: meta.title,
|
||||
description: meta.description,
|
||||
keywords: meta.keywords,
|
||||
@@ -41,9 +42,9 @@ export async function generateMetadata({ params }: PageProps): Promise<Metadata>
|
||||
title: meta.title,
|
||||
description: meta.description,
|
||||
type: "website",
|
||||
siteName: "CommunityRule",
|
||||
siteName: messages.metadata.siteName,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export default async function UseCaseCompletedRulePage({ params }: PageProps) {
|
||||
|
||||
+8
-7
@@ -3,6 +3,7 @@ import type { Metadata, Viewport } from "next";
|
||||
import type { ReactNode } from "react";
|
||||
import messages from "../messages/en/index";
|
||||
import { ASSETS, getAssetPath } from "../lib/assetUtils";
|
||||
import { PRODUCTION_SITE_ORIGIN } from "../lib/siteMetadata";
|
||||
import "./globals.css";
|
||||
|
||||
// `force-dynamic` is now scoped to `(app)/layout.tsx` and `(admin)/layout.tsx`
|
||||
@@ -54,7 +55,10 @@ export const viewport: Viewport = {
|
||||
};
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: homeMeta.title,
|
||||
title: {
|
||||
default: homeMeta.title,
|
||||
template: messages.metadata.titleTemplate,
|
||||
},
|
||||
description: homeMeta.description,
|
||||
keywords: [...homeMeta.keywords],
|
||||
authors: [{ name: "Media Economies Design Lab" }],
|
||||
@@ -65,7 +69,7 @@ export const metadata: Metadata = {
|
||||
address: false,
|
||||
telephone: false,
|
||||
},
|
||||
metadataBase: new URL("https://communityrule.com"),
|
||||
metadataBase: new URL(PRODUCTION_SITE_ORIGIN),
|
||||
icons: {
|
||||
icon: [
|
||||
{ url: getAssetPath(ASSETS.LOGO), type: "image/svg+xml" },
|
||||
@@ -89,14 +93,11 @@ export const metadata: Metadata = {
|
||||
},
|
||||
],
|
||||
},
|
||||
alternates: {
|
||||
canonical: "/",
|
||||
},
|
||||
openGraph: {
|
||||
title: homeMeta.title,
|
||||
description: homeMeta.description,
|
||||
url: "https://communityrule.com",
|
||||
siteName: "CommunityRule",
|
||||
url: PRODUCTION_SITE_ORIGIN,
|
||||
siteName: messages.metadata.siteName,
|
||||
locale: "en_US",
|
||||
type: "website",
|
||||
},
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import messages from "../messages/en/index";
|
||||
import { getTranslation } from "../lib/i18n/getTranslation";
|
||||
import { NO_INDEX_ROBOTS } from "../lib/siteMetadata";
|
||||
import { getGovernanceTemplateCatalogEntry } from "../lib/templates/governanceTemplateCatalog";
|
||||
import Icon from "./components/asset/icon";
|
||||
import Button from "./components/buttons/Button";
|
||||
import HeroDecor from "./components/sections/HeroBanner/HeroDecor";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: messages.metadata.notFound.title,
|
||||
robots: NO_INDEX_ROBOTS,
|
||||
};
|
||||
|
||||
const NOT_FOUND_TEMPLATE_SLUGS = [
|
||||
"consensus",
|
||||
"do-ocracy",
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
import { headers } from "next/headers";
|
||||
import { buildRobots, resolveRequestHost } from "../lib/siteMetadata";
|
||||
|
||||
export default async function robots(): Promise<MetadataRoute.Robots> {
|
||||
const headerStore = await headers();
|
||||
const host = resolveRequestHost(
|
||||
headerStore.get("x-forwarded-host"),
|
||||
headerStore.get("host"),
|
||||
);
|
||||
return buildRobots(host);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
import { buildPublicSitemap } from "../lib/publicSitemap";
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
return buildPublicSitemap();
|
||||
}
|
||||
Reference in New Issue
Block a user