Metadata Canocical pages
Some checks failed
Next.js App CI / docker (push) Failing after 59s

This commit is contained in:
2025-09-22 09:41:53 +02:00
parent 7b53b89c82
commit 941e0a66f3
13 changed files with 90 additions and 23 deletions

View File

@@ -4,9 +4,16 @@ import Image from "next/image";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { BlurFade } from "@/components/magicui/blur-fade"; import { BlurFade } from "@/components/magicui/blur-fade";
import { TrackedLink } from "@/components/util-tracked-link"; import { TrackedLink } from "@/components/util-tracked-link";
import { Metadata } from "next";
const BLUR_FADE_DELAY = 0.01; const BLUR_FADE_DELAY = 0.01;
export const metadata: Metadata = {
alternates: {
canonical: `${DATA.url}/connect`,
},
}
export default function ConnectPage() { export default function ConnectPage() {
const featuredSocials = ["Email", "LinkedIn", "GoogleScholar", "arXiv", "ResearchGate", "Gitea"]; const featuredSocials = ["Email", "LinkedIn", "GoogleScholar", "arXiv", "ResearchGate", "Gitea"];
const socialLinks = Object.entries(DATA.contact.social) const socialLinks = Object.entries(DATA.contact.social)

View File

@@ -9,8 +9,10 @@ type Props = {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>; searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}; };
const page = "experience"
export async function generateStaticParams() { export async function generateStaticParams() {
const slugs = getPostSlugs('experience'); const slugs = getPostSlugs(page);
return slugs.map((slug) => ({ slug })); return slugs.map((slug) => ({ slug }));
} }
@@ -21,13 +23,16 @@ export async function generateMetadata({ params }: Props) {
return {}; return {};
} }
const post = await getPostBySlug('experience', slug); const post = await getPostBySlug(page, slug);
if (!post) { if (!post) {
return {}; return {};
} }
return { return {
title: post.frontmatter.title, title: post.frontmatter.title,
description: post.frontmatter.teaser || DATA.description, description: post.frontmatter.teaser || DATA.description,
alternates: {
canonical: `${DATA.url}/${page}/${slug}`,
},
}; };
} }
@@ -38,7 +43,7 @@ export default async function ExperiencePage({ params }: Props) {
notFound(); notFound();
} }
const post = await getPostBySlug('experience', slug); const post = await getPostBySlug(page, slug);
const publications = getPublicationsData(); const publications = getPublicationsData();
if (!post) { if (!post) {
@@ -46,18 +51,18 @@ export default async function ExperiencePage({ params }: Props) {
} }
// --- Navigation Logic --- // --- Navigation Logic ---
const allSlugs = getPostSlugs('experience'); const allSlugs = getPostSlugs(page);
const currentIndex = allSlugs.findIndex((s) => s === slug); const currentIndex = allSlugs.findIndex((s) => s === slug);
const prevSlug = currentIndex > 0 ? allSlugs[currentIndex - 1] : null; const prevSlug = currentIndex > 0 ? allSlugs[currentIndex - 1] : null;
const nextSlug = currentIndex < allSlugs.length - 1 ? 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 prevPost: Post | null = prevSlug ? await getPostBySlug(page, prevSlug) : null;
const nextPost: Post | null = nextSlug ? await getPostBySlug('experience', nextSlug) : null; const nextPost: Post | null = nextSlug ? await getPostBySlug(page, nextSlug) : null;
const navigation = { const navigation = {
prev: prevPost ? { slug: prevSlug!, title: prevPost.frontmatter.title } : null, prev: prevPost ? { slug: prevSlug!, title: prevPost.frontmatter.title } : null,
next: nextPost ? { slug: nextSlug!, title: nextPost.frontmatter.title } : null, next: nextPost ? { slug: nextSlug!, title: nextPost.frontmatter.title } : null,
}; };
return <Article post={post} publications={publications} navigation={navigation} basePath="experience" />; return <Article post={post} publications={publications} navigation={navigation} basePath={page} />;
} }

View File

@@ -2,6 +2,14 @@
import { getAllTags, getSortedPostsData } from "@/lib/posts"; import { getAllTags, getSortedPostsData } from "@/lib/posts";
import { FilterableExperienceGrid } from "@/components/filterable-experience-list"; 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() { export default function ExperiencePage() {
const posts = getSortedPostsData("experience"); const posts = getSortedPostsData("experience");

View File

@@ -21,10 +21,10 @@ export const metadata: Metadata = {
}, },
description: DATA.description, description: DATA.description,
openGraph: { openGraph: {
title: `${DATA.name}`, title: DATA.name,
description: DATA.description, description: DATA.description,
url: DATA.url, url: DATA.url,
siteName: `${DATA.name}`, siteName: DATA.name,
locale: "en_US", locale: "en_US",
type: "website", type: "website",
}, },
@@ -39,8 +39,11 @@ export const metadata: Metadata = {
"max-snippet": -1, "max-snippet": -1,
}, },
}, },
alternates: {
canonical: DATA.url,
},
twitter: { twitter: {
title: `${DATA.name}`, title: DATA.name,
card: "summary_large_image", card: "summary_large_image",
}, },
verification: { verification: {

View File

@@ -5,6 +5,13 @@ import { Badge } from "@/components/ui/badge";
import fs from "fs"; import fs from "fs";
import path from "path"; import path from "path";
import { TrackedLink } from "@/components/util-tracked-link"; import { TrackedLink } from "@/components/util-tracked-link";
import { Metadata } from "next";
export const metadata: Metadata = {
alternates: {
canonical: `${DATA.url}/publications`,
},
}
export default function PublicationsPage() { export default function PublicationsPage() {
const publicationsRaw = getPublicationsData(); const publicationsRaw = getPublicationsData();

View File

@@ -7,26 +7,31 @@ import { Article } from '@/components/page-article';
import { DATA } from '@/app/resume'; import { DATA } from '@/app/resume';
import { Props } from '@/components/types'; import { Props } from '@/components/types';
const page = "research"
export async function generateStaticParams() { export async function generateStaticParams() {
const slugs = getPostSlugs('research'); const slugs = getPostSlugs(page);
return slugs.map((slug) => ({ slug })); return slugs.map((slug) => ({ slug }));
} }
export async function generateMetadata({ params }: Props ) { export async function generateMetadata({ params }: Props ) {
const { slug } = await params; const { slug } = await params;
const post = await getPostBySlug('research', slug); const post = await getPostBySlug(page, slug);
if (!post) { return {}; } if (!post) { return {}; }
return { return {
title: post.frontmatter.title, title: post.frontmatter.title,
description: post.frontmatter.teaser || DATA.description, description: post.frontmatter.teaser || DATA.description,
alternates: {
canonical: `${DATA.url}/${page}/${slug}`,
},
}; };
} }
export default async function ResearchPage({ params }: Props ) { export default async function ResearchPage({ params }: Props ) {
const { slug } = await params; const { slug } = await params;
const post = await getPostBySlug('research', slug); const post = await getPostBySlug(page, slug);
const publications = getPublicationsData(); const publications = getPublicationsData();
if (!post) { if (!post) {
@@ -34,18 +39,18 @@ export default async function ResearchPage({ params }: Props ) {
} }
// --- Navigation Logic --- // --- Navigation Logic ---
const allSlugs = getPostSlugs('research'); const allSlugs = getPostSlugs(page);
const currentIndex = allSlugs.findIndex((s) => s === slug); const currentIndex = allSlugs.findIndex((s) => s === slug);
const prevSlug = currentIndex > 0 ? allSlugs[currentIndex - 1] : null; const prevSlug = currentIndex > 0 ? allSlugs[currentIndex - 1] : null;
const nextSlug = currentIndex < allSlugs.length - 1 ? allSlugs[currentIndex + 1] : null; const nextSlug = currentIndex < allSlugs.length - 1 ? allSlugs[currentIndex + 1] : null;
const prevPost = prevSlug ? await getPostBySlug('research', prevSlug) : null; const prevPost = prevSlug ? await getPostBySlug(page, prevSlug) : null;
const nextPost = nextSlug ? await getPostBySlug('research', nextSlug) : null; const nextPost = nextSlug ? await getPostBySlug(page, nextSlug) : null;
const navigation = { const navigation = {
prev: prevPost ? { slug: prevSlug!, title: prevPost.frontmatter.title } : null, prev: prevPost ? { slug: prevSlug!, title: prevPost.frontmatter.title } : null,
next: nextPost ? { slug: nextSlug!, title: nextPost.frontmatter.title } : null, next: nextPost ? { slug: nextSlug!, title: nextPost.frontmatter.title } : null,
}; };
return <Article post={post} publications={publications} navigation={navigation} basePath="research" />; return <Article post={post} publications={publications} navigation={navigation} basePath={page} />;
} }

View File

@@ -2,6 +2,14 @@
import { getSortedPostsData, getAllTags } from "@/lib/posts"; import { getSortedPostsData, getAllTags } from "@/lib/posts";
import { FilterableResearchGrid } from "@/components/filterable-research-list"; // Import the new client component 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() { export default function ResearchPage() {
// These functions run safely on the server because this is a Server Component. // These functions run safely on the server because this is a Server Component.

View File

@@ -6,10 +6,13 @@ import {
ClipboardListIcon, ClipboardListIcon,
} from "lucide-react"; } from "lucide-react";
const domain: string = "steffenillium.de"
export const DATA = { export const DATA = {
name: "Steffen Illium", name: "Steffen Illium",
initials: "SI", initials: "SI",
url: "https://steffenillium.de", url: `https://${domain}.de`,
domain: `${domain}`,
location: "Augsburg, Germany", location: "Augsburg, Germany",
locationLink: "https://www.google.com/maps/place/Augsburg", locationLink: "https://www.google.com/maps/place/Augsburg",
description: description:

View File

@@ -1,10 +1,10 @@
// app/sitemap.ts // app/sitemap.ts
import { getAllTags, getSortedPostsData } from '@/lib/posts'; import { getAllTags, getSortedPostsData } from '@/lib/posts';
import { MetadataRoute } from 'next'; import { MetadataRoute } from 'next';
import { DATA } from './resume';
export default function sitemap(): MetadataRoute.Sitemap { 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 // Improvement 1: Fetch all posts with a single, more efficient call
const allPosts = getSortedPostsData(); const allPosts = getSortedPostsData();
const postUrls: MetadataRoute.Sitemap = allPosts.map((post) => ({ const postUrls: MetadataRoute.Sitemap = allPosts.map((post) => ({

View File

@@ -1,7 +1,15 @@
import { BlurFade } from "@/components/magicui/blur-fade"; import { BlurFade } from "@/components/magicui/blur-fade";
import { DATA } from "../resume";
import { Metadata } from "next";
const BLUR_FADE_DELAY = 0.01; const BLUR_FADE_DELAY = 0.01;
export const metadata: Metadata = {
alternates: {
canonical: `${DATA.url}/status`,
},
}
export default function StatusPage() { export default function StatusPage() {
return ( return (

View File

@@ -4,8 +4,9 @@ import { BlurFade } from "@/components/magicui/blur-fade";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { Breadcrumbs } from "@/components/element-breadcrumbs"; import { Breadcrumbs } from "@/components/element-breadcrumbs";
import { TagProps } from "@/components/types"; import { TagProps } from "@/components/types";
import { DATA } from "@/app/resume";
const page = "tags"
const BLUR_FADE_DELAY = 0.01; const BLUR_FADE_DELAY = 0.01;
export async function generateStaticParams() { export async function generateStaticParams() {
@@ -20,6 +21,9 @@ export async function generateMetadata({ params }: TagProps ) {
return { return {
title: tag, title: tag,
description: tagData?.definition || `Posts tagged with ${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 { tag } = await params;
const posts = getPostsByTag(tag); const posts = getPostsByTag(tag);
const tagData = getTagData(tag); const tagData = getTagData(tag);
const basePath = "tags"; const basePath = page;
if (posts.length === 0) { if (posts.length === 0) {
return notFound(); return notFound();

View File

@@ -1,9 +1,17 @@
import { getAllTags } from "@/lib/posts"; import { getAllTags } from "@/lib/posts";
import Link from "next/link"; import Link from "next/link";
import { BlurFade } from "@/components/magicui/blur-fade"; import { BlurFade } from "@/components/magicui/blur-fade";
import { Metadata } from "next";
import { DATA } from "../resume";
const BLUR_FADE_DELAY = 0.01; const BLUR_FADE_DELAY = 0.01;
export const metadata: Metadata = {
alternates: {
canonical: `${DATA.url}/tags`,
},
}
export default function TagsPage() { export default function TagsPage() {
const tags = getAllTags(2); const tags = getAllTags(2);

View File

@@ -1,4 +1,5 @@
import type { NextConfig } from "next"; import type { NextConfig } from "next";
import { DATA } from "./app/resume";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
/* your other config options here */ /* your other config options here */
@@ -7,7 +8,7 @@ const nextConfig: NextConfig = {
remotePatterns: [ remotePatterns: [
{ {
protocol: "https", protocol: "https",
hostname: "*.steffenillium.de", hostname: `*.${DATA.domain}`,
}, },
], ],
}, },