diff --git a/src/app/(public)/events/calendar/page.tsx b/src/app/(public)/events/calendar/page.tsx index 41d1817..31fe6a1 100644 --- a/src/app/(public)/events/calendar/page.tsx +++ b/src/app/(public)/events/calendar/page.tsx @@ -2,7 +2,7 @@ import type { Metadata } from "next"; import { Calendar, ExternalLink } from "lucide-react"; import { siteConfig } from "@/config/site"; -import { createPageMetadata } from "@/lib/metadata"; +import { createPageMetadata, pageDescriptions } from "@/lib/metadata"; import { Button } from "@/components/ui/button"; // ============================================================================ @@ -11,8 +11,7 @@ import { Button } from "@/components/ui/button"; export const metadata: Metadata = createPageMetadata({ title: "Events Calendar", - description: - "Upcoming events, seminars, and meetings at the Materials and Process Simulation Center.", + description: pageDescriptions.calendar, path: "/events/calendar", }); diff --git a/src/app/(public)/events/photos/page.tsx b/src/app/(public)/events/photos/page.tsx index bc0731f..8e59a2e 100644 --- a/src/app/(public)/events/photos/page.tsx +++ b/src/app/(public)/events/photos/page.tsx @@ -2,7 +2,7 @@ import type { Metadata } from "next"; import { Camera, Calendar as CalendarIcon } from "lucide-react"; import { getPhotosGroupedByYear, getPhotoStats } from "@/lib/db/queries/photos"; -import { createPageMetadata } from "@/lib/metadata"; +import { createPageMetadata, pageDescriptions } from "@/lib/metadata"; import { PhotoGallery } from "@/components/events"; // ============================================================================ @@ -11,8 +11,7 @@ import { PhotoGallery } from "@/components/events"; export const metadata: Metadata = createPageMetadata({ title: "Group Photos", - description: - "Photo gallery of the Materials and Process Simulation Center team through the years.", + description: pageDescriptions.photos, path: "/events/photos", }); diff --git a/src/app/admin/(authenticated)/admins/page.tsx b/src/app/admin/(authenticated)/admins/page.tsx index 8f1e5fc..83cce27 100644 --- a/src/app/admin/(authenticated)/admins/page.tsx +++ b/src/app/admin/(authenticated)/admins/page.tsx @@ -1,4 +1,5 @@ import { Suspense } from "react"; +import type { Metadata } from "next"; import { PageHeader } from "@/components/admin/layout"; import { AdminListSkeleton } from "@/components/admin/shared"; @@ -10,8 +11,8 @@ import { AdminList, AccountView } from "./_components"; // Metadata // ============================================================================ -export const metadata = { - title: "Administrators | Admin", +export const metadata: Metadata = { + title: "Administrators", description: "Manage administrator accounts and roles", }; diff --git a/src/app/admin/(authenticated)/collaborators/page.tsx b/src/app/admin/(authenticated)/collaborators/page.tsx index ffd245b..ebf31b3 100644 --- a/src/app/admin/(authenticated)/collaborators/page.tsx +++ b/src/app/admin/(authenticated)/collaborators/page.tsx @@ -1,4 +1,5 @@ import { Suspense } from "react"; +import type { Metadata } from "next"; import { PageHeader } from "@/components/admin/layout"; import { CollaboratorListSkeleton } from "@/components/admin/shared"; @@ -9,8 +10,8 @@ import { CollaboratorList } from "./_components"; // Metadata // ============================================================================ -export const metadata = { - title: "Collaborators | Admin", +export const metadata: Metadata = { + title: "Collaborators", description: "Manage collaborators and their locations", }; diff --git a/src/app/admin/(authenticated)/layout.tsx b/src/app/admin/(authenticated)/layout.tsx index 4805bda..16392a6 100644 --- a/src/app/admin/(authenticated)/layout.tsx +++ b/src/app/admin/(authenticated)/layout.tsx @@ -1,8 +1,22 @@ import { redirect } from "next/navigation"; +import type { Metadata } from "next"; import { getCurrentUser } from "@/lib/auth"; +import { siteConfig } from "@/config/site"; import { Sidebar, TopBar, AuthKeepAlive } from "@/components/admin/layout"; +// ============================================================================ +// Metadata +// ============================================================================ + +export const metadata: Metadata = { + title: { + template: `%s | Admin | ${siteConfig.name}`, + default: `Admin | ${siteConfig.name}`, + }, + robots: "noindex,nofollow", +}; + // ============================================================================ // Route Config // ============================================================================ diff --git a/src/app/admin/(authenticated)/members/categories/page.tsx b/src/app/admin/(authenticated)/members/categories/page.tsx index af65f67..ec82f1b 100644 --- a/src/app/admin/(authenticated)/members/categories/page.tsx +++ b/src/app/admin/(authenticated)/members/categories/page.tsx @@ -1,4 +1,5 @@ import { Suspense } from "react"; +import type { Metadata } from "next"; import { PageHeader } from "@/components/admin/layout"; import { ListSkeleton } from "@/components/admin/shared"; @@ -9,8 +10,8 @@ import { CategoryList } from "./_components"; // Metadata // ============================================================================ -export const metadata = { - title: "Member Categories | Admin", +export const metadata: Metadata = { + title: "Member Categories", description: "Manage member categories and display order", }; diff --git a/src/app/admin/(authenticated)/members/page.tsx b/src/app/admin/(authenticated)/members/page.tsx index afc7654..88f001f 100644 --- a/src/app/admin/(authenticated)/members/page.tsx +++ b/src/app/admin/(authenticated)/members/page.tsx @@ -1,5 +1,6 @@ import { Suspense } from "react"; import Link from "next/link"; +import type { Metadata } from "next"; import { Settings } from "lucide-react"; import { PageHeader } from "@/components/admin/layout"; @@ -12,8 +13,8 @@ import { MemberList } from "./_components"; // Metadata // ============================================================================ -export const metadata = { - title: "Members | Admin", +export const metadata: Metadata = { + title: "Members", description: "Manage team members and their profiles", }; diff --git a/src/app/admin/(authenticated)/page.tsx b/src/app/admin/(authenticated)/page.tsx index c1257a8..84528fa 100644 --- a/src/app/admin/(authenticated)/page.tsx +++ b/src/app/admin/(authenticated)/page.tsx @@ -1,3 +1,5 @@ +import type { Metadata } from "next"; + import { getDashboardStats } from "@/lib/admin/actions"; import { PublicationChart, RecentActivity, StatsGrid } from "./_components"; @@ -5,8 +7,8 @@ import { PublicationChart, RecentActivity, StatsGrid } from "./_components"; // Metadata // ============================================================================ -export const metadata = { - title: "Dashboard | Admin", +export const metadata: Metadata = { + title: "Dashboard", description: "Overview of your site content and activity", }; diff --git a/src/app/admin/(authenticated)/photos/page.tsx b/src/app/admin/(authenticated)/photos/page.tsx index 829d75a..2ed7ef2 100644 --- a/src/app/admin/(authenticated)/photos/page.tsx +++ b/src/app/admin/(authenticated)/photos/page.tsx @@ -1,4 +1,5 @@ import { Suspense } from "react"; +import type { Metadata } from "next"; import { PageHeader } from "@/components/admin/layout"; import { PhotoGridSkeleton } from "@/components/admin/shared"; @@ -9,8 +10,8 @@ import { PhotoList } from "./_components"; // Metadata // ============================================================================ -export const metadata = { - title: "Group Photos | Admin", +export const metadata: Metadata = { + title: "Group Photos", description: "Manage group photos and display order", }; diff --git a/src/app/admin/(authenticated)/publications/page.tsx b/src/app/admin/(authenticated)/publications/page.tsx index 8d8f81c..34f0f2e 100644 --- a/src/app/admin/(authenticated)/publications/page.tsx +++ b/src/app/admin/(authenticated)/publications/page.tsx @@ -1,4 +1,5 @@ import { Suspense } from "react"; +import type { Metadata } from "next"; import { PageHeader } from "@/components/admin/layout"; import { PublicationListSkeleton } from "@/components/admin/shared"; @@ -13,8 +14,8 @@ import { PublicationList } from "./_components"; // Metadata // ============================================================================ -export const metadata = { - title: "Publications | Admin", +export const metadata: Metadata = { + title: "Publications", description: "Manage research publications", }; diff --git a/src/app/admin/(authenticated)/research/page.tsx b/src/app/admin/(authenticated)/research/page.tsx index 36d3038..63da438 100644 --- a/src/app/admin/(authenticated)/research/page.tsx +++ b/src/app/admin/(authenticated)/research/page.tsx @@ -1,4 +1,5 @@ import { Suspense } from "react"; +import type { Metadata } from "next"; import { PageHeader } from "@/components/admin/layout"; import { ResearchTreeSkeleton } from "@/components/admin/shared"; @@ -9,8 +10,8 @@ import { ResearchList } from "./_components"; // Metadata // ============================================================================ -export const metadata = { - title: "Research Areas | Admin", +export const metadata: Metadata = { + title: "Research Areas", description: "Manage research areas and their hierarchy", }; diff --git a/src/app/admin/(authenticated)/sync/page.tsx b/src/app/admin/(authenticated)/sync/page.tsx index be50efe..d2351e0 100644 --- a/src/app/admin/(authenticated)/sync/page.tsx +++ b/src/app/admin/(authenticated)/sync/page.tsx @@ -1,3 +1,5 @@ +import type { Metadata } from "next"; + import { getSyncStats } from "@/lib/admin/actions"; import { PageHeader } from "@/components/admin/layout"; import { SyncDashboard } from "./_components"; @@ -6,8 +8,8 @@ import { SyncDashboard } from "./_components"; // Metadata // ============================================================================ -export const metadata = { - title: "Data Sync | Admin", +export const metadata: Metadata = { + title: "Data Sync", description: "Sync publication metadata and rebuild data relationships", }; diff --git a/src/config/site.ts b/src/config/site.ts index 97b2ba6..b975e18 100644 --- a/src/config/site.ts +++ b/src/config/site.ts @@ -77,19 +77,3 @@ export const mainNav: NavItem[] = [ ], }, ] as const; - -// ============================================================================ -// SEO Defaults -// ============================================================================ - -export const seoDefaults = { - defaultTitle: `${siteConfig.name} - ${siteConfig.fullName}`, - openGraph: { - type: "website", - locale: "en_US", - siteName: siteConfig.name, - }, - twitter: { - card: "summary_large_image", - }, -} as const; diff --git a/src/lib/metadata.ts b/src/lib/metadata.ts index 68401a0..a77884e 100644 --- a/src/lib/metadata.ts +++ b/src/lib/metadata.ts @@ -5,7 +5,7 @@ */ import type { Metadata } from "next"; -import { siteConfig, seoDefaults } from "@/config/site"; +import { siteConfig } from "@/config/site"; import { truncateAtWordBoundary } from "@/lib/format"; import { getYear } from "@/lib/date"; @@ -24,43 +24,42 @@ interface PageMetadataOptions { /** * Generate metadata for a page. */ -export function createPageMetadata( - options: PageMetadataOptions = {} -): Metadata { - const { - title, - description = siteConfig.description, - path = "", - image = siteConfig.images.ogImage, - noIndex = false, - } = options; - - const fullTitle = title - ? `${title} | ${siteConfig.name}` - : seoDefaults.defaultTitle; - - const url = `${siteConfig.url}${path}`; +export function createPageMetadata({ + title, + description, + path, + image, + noIndex = false, +}: PageMetadataOptions = {}): Metadata { + const ogTitle = title ? `${title} | ${siteConfig.name}` : undefined; + const url = path !== undefined ? `${siteConfig.url}${path}` : undefined; + const hasContent = !!(ogTitle || description); + const resolvedImage = image ?? siteConfig.images.ogImage; return { - title: fullTitle, - description, + ...(title && { title }), + ...(description && { description }), ...(noIndex && { robots: "noindex,nofollow" }), - openGraph: { - ...seoDefaults.openGraph, - title: fullTitle, - description, - url, - images: image ? [{ url: image }] : undefined, - }, - twitter: { - ...seoDefaults.twitter, - title: fullTitle, - description, - images: image ? [image] : undefined, - }, - alternates: { - canonical: url, - }, + ...(hasContent && { + openGraph: { + type: "website", + locale: "en_US", + siteName: siteConfig.name, + ...(ogTitle && { title: ogTitle }), + ...(description && { description }), + ...(url && { url }), + images: [{ url: resolvedImage }], + }, + }), + ...(hasContent && { + twitter: { + card: "summary_large_image", + ...(ogTitle && { title: ogTitle }), + ...(description && { description }), + images: [resolvedImage], + }, + }), + ...(url && { alternates: { canonical: url } }), }; } @@ -74,16 +73,15 @@ export function createMemberMetadata(member: { bio?: string | null; photo?: string | null; }): Metadata { - const title = member.name; const description = member.bio ? truncateAtWordBoundary(member.bio, 160) : `${member.name}${member.position ? ` - ${member.position}` : ""} at ${siteConfig.fullName}, Caltech.`; return createPageMetadata({ - title, + title: member.name, description, path: `/members/${member.id}`, - image: member.photo || siteConfig.images.ogImage, + ...(member.photo && { image: member.photo }), }); } @@ -155,6 +153,8 @@ export const pageDescriptions = { research: `Explore research areas at ${siteConfig.fullName}, Caltech.`, collaborators: `Global research partners and collaborators of ${siteConfig.fullName}.`, events: `Events, group photos, and calendar for ${siteConfig.fullName}.`, + photos: `Photo gallery of ${siteConfig.fullName} team through the years.`, + calendar: `Upcoming events, seminars, and meetings at ${siteConfig.fullName}.`, wag: `Prof. William A. Goddard III, Director of ${siteConfig.fullName} at Caltech.`, msc: `About ${siteConfig.fullName} at the California Institute of Technology.`, } as const;