From 941e0a66f322745da08e326571bc587fe0381819 Mon Sep 17 00:00:00 2001 From: Steffen Illium Date: Mon, 22 Sep 2025 09:41:53 +0200 Subject: [PATCH] Metadata Canocical pages --- app/connect/page.tsx | 7 +++++++ app/experience/[slug]/page.tsx | 19 ++++++++++++------- app/experience/page.tsx | 8 ++++++++ app/layout.tsx | 9 ++++++--- app/publications/page.tsx | 7 +++++++ app/research/[slug]/page.tsx | 19 ++++++++++++------- app/research/page.tsx | 8 ++++++++ app/resume.tsx | 5 ++++- app/sitemap.ts | 4 ++-- app/status/page.tsx | 8 ++++++++ app/tags/[tag]/page.tsx | 8 ++++++-- app/tags/page.tsx | 8 ++++++++ next.config.ts | 3 ++- 13 files changed, 90 insertions(+), 23 deletions(-) diff --git a/app/connect/page.tsx b/app/connect/page.tsx index e0bc7536..bdc42d04 100644 --- a/app/connect/page.tsx +++ b/app/connect/page.tsx @@ -4,9 +4,16 @@ import Image from "next/image"; import { Button } from "@/components/ui/button"; import { BlurFade } from "@/components/magicui/blur-fade"; import { TrackedLink } from "@/components/util-tracked-link"; +import { Metadata } from "next"; const BLUR_FADE_DELAY = 0.01; +export const metadata: Metadata = { + alternates: { + canonical: `${DATA.url}/connect`, + }, +} + export default function ConnectPage() { const featuredSocials = ["Email", "LinkedIn", "GoogleScholar", "arXiv", "ResearchGate", "Gitea"]; const socialLinks = Object.entries(DATA.contact.social) diff --git a/app/experience/[slug]/page.tsx b/app/experience/[slug]/page.tsx index a41a62e2..c58b21eb 100644 --- a/app/experience/[slug]/page.tsx +++ b/app/experience/[slug]/page.tsx @@ -9,8 +9,10 @@ type Props = { searchParams: Promise<{ [key: string]: string | string[] | undefined }>; }; +const page = "experience" + export async function generateStaticParams() { - const slugs = getPostSlugs('experience'); + const slugs = getPostSlugs(page); return slugs.map((slug) => ({ slug })); } @@ -21,13 +23,16 @@ export async function generateMetadata({ params }: Props) { return {}; } - const post = await getPostBySlug('experience', slug); + const post = await getPostBySlug(page, slug); if (!post) { return {}; } return { title: post.frontmatter.title, description: post.frontmatter.teaser || DATA.description, + alternates: { + canonical: `${DATA.url}/${page}/${slug}`, + }, }; } @@ -38,7 +43,7 @@ export default async function ExperiencePage({ params }: Props) { notFound(); } - const post = await getPostBySlug('experience', slug); + const post = await getPostBySlug(page, slug); const publications = getPublicationsData(); if (!post) { @@ -46,18 +51,18 @@ export default async function ExperiencePage({ params }: Props) { } // --- Navigation Logic --- - const allSlugs = getPostSlugs('experience'); + const allSlugs = getPostSlugs(page); const currentIndex = allSlugs.findIndex((s) => s === slug); const prevSlug = currentIndex > 0 ? allSlugs[currentIndex - 1] : null; const nextSlug = currentIndex < allSlugs.length - 1 ? allSlugs[currentIndex + 1] : null; - const prevPost: Post | null = prevSlug ? await getPostBySlug('experience', prevSlug) : null; - const nextPost: Post | null = nextSlug ? await getPostBySlug('experience', nextSlug) : null; + const prevPost: Post | null = prevSlug ? await getPostBySlug(page, prevSlug) : null; + const nextPost: Post | null = nextSlug ? await getPostBySlug(page, nextSlug) : null; const navigation = { prev: prevPost ? { slug: prevSlug!, title: prevPost.frontmatter.title } : null, next: nextPost ? { slug: nextSlug!, title: nextPost.frontmatter.title } : null, }; - return
; + return
; } \ No newline at end of file diff --git a/app/experience/page.tsx b/app/experience/page.tsx index f775e95c..2f1d7a7a 100644 --- a/app/experience/page.tsx +++ b/app/experience/page.tsx @@ -2,6 +2,14 @@ import { getAllTags, getSortedPostsData } from "@/lib/posts"; import { FilterableExperienceGrid } from "@/components/filterable-experience-list"; +import { DATA } from "../resume"; +import { Metadata } from "next"; + +export const metadata: Metadata = { + alternates: { + canonical: `${DATA.url}/experience`, + }, +} export default function ExperiencePage() { const posts = getSortedPostsData("experience"); diff --git a/app/layout.tsx b/app/layout.tsx index 33065d0a..f340b34b 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -21,10 +21,10 @@ export const metadata: Metadata = { }, description: DATA.description, openGraph: { - title: `${DATA.name}`, + title: DATA.name, description: DATA.description, url: DATA.url, - siteName: `${DATA.name}`, + siteName: DATA.name, locale: "en_US", type: "website", }, @@ -39,8 +39,11 @@ export const metadata: Metadata = { "max-snippet": -1, }, }, + alternates: { + canonical: DATA.url, + }, twitter: { - title: `${DATA.name}`, + title: DATA.name, card: "summary_large_image", }, verification: { diff --git a/app/publications/page.tsx b/app/publications/page.tsx index 45389182..203bca2d 100644 --- a/app/publications/page.tsx +++ b/app/publications/page.tsx @@ -5,6 +5,13 @@ import { Badge } from "@/components/ui/badge"; import fs from "fs"; import path from "path"; import { TrackedLink } from "@/components/util-tracked-link"; +import { Metadata } from "next"; + +export const metadata: Metadata = { + alternates: { + canonical: `${DATA.url}/publications`, + }, +} export default function PublicationsPage() { const publicationsRaw = getPublicationsData(); diff --git a/app/research/[slug]/page.tsx b/app/research/[slug]/page.tsx index cac96391..813bedc9 100644 --- a/app/research/[slug]/page.tsx +++ b/app/research/[slug]/page.tsx @@ -7,26 +7,31 @@ import { Article } from '@/components/page-article'; import { DATA } from '@/app/resume'; import { Props } from '@/components/types'; +const page = "research" + export async function generateStaticParams() { - const slugs = getPostSlugs('research'); + const slugs = getPostSlugs(page); return slugs.map((slug) => ({ slug })); } export async function generateMetadata({ params }: Props ) { const { slug } = await params; - const post = await getPostBySlug('research', slug); + const post = await getPostBySlug(page, slug); if (!post) { return {}; } return { title: post.frontmatter.title, description: post.frontmatter.teaser || DATA.description, + alternates: { + canonical: `${DATA.url}/${page}/${slug}`, + }, }; } export default async function ResearchPage({ params }: Props ) { const { slug } = await params; - const post = await getPostBySlug('research', slug); + const post = await getPostBySlug(page, slug); const publications = getPublicationsData(); if (!post) { @@ -34,18 +39,18 @@ export default async function ResearchPage({ params }: Props ) { } // --- Navigation Logic --- - const allSlugs = getPostSlugs('research'); + const allSlugs = getPostSlugs(page); const currentIndex = allSlugs.findIndex((s) => s === slug); const prevSlug = currentIndex > 0 ? allSlugs[currentIndex - 1] : null; const nextSlug = currentIndex < allSlugs.length - 1 ? allSlugs[currentIndex + 1] : null; - const prevPost = prevSlug ? await getPostBySlug('research', prevSlug) : null; - const nextPost = nextSlug ? await getPostBySlug('research', nextSlug) : null; + const prevPost = prevSlug ? await getPostBySlug(page, prevSlug) : null; + const nextPost = nextSlug ? await getPostBySlug(page, nextSlug) : null; const navigation = { prev: prevPost ? { slug: prevSlug!, title: prevPost.frontmatter.title } : null, next: nextPost ? { slug: nextSlug!, title: nextPost.frontmatter.title } : null, }; - return
; + return
; } \ No newline at end of file diff --git a/app/research/page.tsx b/app/research/page.tsx index 9b87e13f..49abb3ab 100644 --- a/app/research/page.tsx +++ b/app/research/page.tsx @@ -2,6 +2,14 @@ import { getSortedPostsData, getAllTags } from "@/lib/posts"; import { FilterableResearchGrid } from "@/components/filterable-research-list"; // Import the new client component +import { DATA } from "../resume"; +import { Metadata } from "next"; + +export const metadata: Metadata = { + alternates: { + canonical: `${DATA.url}/research`, + }, +} export default function ResearchPage() { // These functions run safely on the server because this is a Server Component. diff --git a/app/resume.tsx b/app/resume.tsx index 6a9d4d35..a925e915 100644 --- a/app/resume.tsx +++ b/app/resume.tsx @@ -6,10 +6,13 @@ import { ClipboardListIcon, } from "lucide-react"; +const domain: string = "steffenillium.de" + export const DATA = { name: "Steffen Illium", initials: "SI", - url: "https://steffenillium.de", + url: `https://${domain}.de`, + domain: `${domain}`, location: "Augsburg, Germany", locationLink: "https://www.google.com/maps/place/Augsburg", description: diff --git a/app/sitemap.ts b/app/sitemap.ts index 7b35d92f..3ea7c3b9 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -1,10 +1,10 @@ // app/sitemap.ts import { getAllTags, getSortedPostsData } from '@/lib/posts'; import { MetadataRoute } from 'next'; +import { DATA } from './resume'; export default function sitemap(): MetadataRoute.Sitemap { - const baseUrl = 'https://steffenillium.de'; - + const baseUrl = DATA.url; // Improvement 1: Fetch all posts with a single, more efficient call const allPosts = getSortedPostsData(); const postUrls: MetadataRoute.Sitemap = allPosts.map((post) => ({ diff --git a/app/status/page.tsx b/app/status/page.tsx index 33a949bf..9fddfa1b 100644 --- a/app/status/page.tsx +++ b/app/status/page.tsx @@ -1,7 +1,15 @@ import { BlurFade } from "@/components/magicui/blur-fade"; +import { DATA } from "../resume"; +import { Metadata } from "next"; const BLUR_FADE_DELAY = 0.01; +export const metadata: Metadata = { + alternates: { + canonical: `${DATA.url}/status`, + }, +} + export default function StatusPage() { return ( diff --git a/app/tags/[tag]/page.tsx b/app/tags/[tag]/page.tsx index 0ccb2a0e..8f25c977 100644 --- a/app/tags/[tag]/page.tsx +++ b/app/tags/[tag]/page.tsx @@ -4,8 +4,9 @@ import { BlurFade } from "@/components/magicui/blur-fade"; import { notFound } from "next/navigation"; import { Breadcrumbs } from "@/components/element-breadcrumbs"; import { TagProps } from "@/components/types"; +import { DATA } from "@/app/resume"; - +const page = "tags" const BLUR_FADE_DELAY = 0.01; export async function generateStaticParams() { @@ -20,6 +21,9 @@ export async function generateMetadata({ params }: TagProps ) { return { title: tag, description: tagData?.definition || `Posts tagged with ${tag}`, + alternates: { + canonical: `${DATA.url}/${page}/${tag}`, + }, }; } @@ -27,7 +31,7 @@ export default async function TagPage({ params }: TagProps) { const { tag } = await params; const posts = getPostsByTag(tag); const tagData = getTagData(tag); - const basePath = "tags"; + const basePath = page; if (posts.length === 0) { return notFound(); diff --git a/app/tags/page.tsx b/app/tags/page.tsx index 34a5b06a..7ca5cb75 100644 --- a/app/tags/page.tsx +++ b/app/tags/page.tsx @@ -1,9 +1,17 @@ import { getAllTags } from "@/lib/posts"; import Link from "next/link"; import { BlurFade } from "@/components/magicui/blur-fade"; +import { Metadata } from "next"; +import { DATA } from "../resume"; const BLUR_FADE_DELAY = 0.01; +export const metadata: Metadata = { + alternates: { + canonical: `${DATA.url}/tags`, + }, +} + export default function TagsPage() { const tags = getAllTags(2); diff --git a/next.config.ts b/next.config.ts index 7de0dc61..55d1aa5c 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,4 +1,5 @@ import type { NextConfig } from "next"; +import { DATA } from "./app/resume"; const nextConfig: NextConfig = { /* your other config options here */ @@ -7,7 +8,7 @@ const nextConfig: NextConfig = { remotePatterns: [ { protocol: "https", - hostname: "*.steffenillium.de", + hostname: `*.${DATA.domain}`, }, ], },