feat: add SEO fields to settings configuration

This commit is contained in:
ralksta
2026-03-08 16:19:41 +01:00
parent 01da5e1baa
commit 270768d330
3 changed files with 53 additions and 19 deletions

View File

@@ -14,28 +14,36 @@ import { Footer } from '@/components/Footer';
import { DevToolbar } from '@/components/DevToolbar';
import { getConfig, getGoogleFontsUrl } from '@/lib/config';
const siteTitle = process.env.SITE_TITLE || 'Gallery';
const siteDescription = 'A curated photography portfolio';
export async function generateMetadata(): Promise<Metadata> {
const config = getConfig();
const siteTitle = config.seo.title;
const siteDescription = config.seo.description;
const robots = {
index: !config.seo.noIndex,
follow: !config.seo.noFollow,
};
export const metadata: Metadata = {
title: siteTitle,
description: siteDescription,
icons: {
apple: '/apple-touch-icon.png',
},
openGraph: {
return {
title: siteTitle,
description: siteDescription,
type: 'website',
images: [`/api/og?title=${encodeURIComponent(siteTitle)}`],
},
twitter: {
card: 'summary_large_image',
title: siteTitle,
description: siteDescription,
images: [`/api/og?title=${encodeURIComponent(siteTitle)}`],
},
};
robots,
icons: {
apple: '/apple-touch-icon.png',
},
openGraph: {
title: siteTitle,
description: siteDescription,
type: 'website',
images: [`/api/og?title=${encodeURIComponent(siteTitle)}`],
},
twitter: {
card: 'summary_large_image',
title: siteTitle,
description: siteDescription,
images: [`/api/og?title=${encodeURIComponent(siteTitle)}`],
},
};
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
const config = getConfig();

View File

@@ -6,6 +6,14 @@
title: "My Portfolio"
subtitle: "A visual journal"
# ── SEO & Metadata ───────────────────────────────────────────────
# Optional: Overrides global title/subtitle for search engines and social cards
# seo:
# title: "My Portfolio | Photography"
# description: "A curated selection of my best photography work."
# noIndex: false # Set to true to hide from search engines
# noFollow: false # Set to true to prevent search engines from following links
# Show EXIF metadata (camera, lens, ISO) on photo grid hover
exifOnHover: true

View File

@@ -154,6 +154,12 @@ interface GalleryYaml {
interface SettingsYaml {
title?: string;
subtitle?: string;
seo?: {
title?: string;
description?: string;
noIndex?: boolean;
noFollow?: boolean;
};
exifOnHover?: boolean;
map?: boolean;
transitions?: boolean;
@@ -245,6 +251,12 @@ export interface AppConfig {
subpages: SubpageConfig[];
siteTitle: string;
siteSubtitle: string;
seo: {
title: string;
description: string;
noIndex: boolean;
noFollow: boolean;
};
/** One or more hero image asset IDs for the homepage carousel. */
heroImages: string[];
/** Show EXIF (camera/lens) on photo grid hover. Default: true. */
@@ -472,6 +484,12 @@ export function getConfig(): AppConfig {
subpages,
siteTitle: settings.title ?? env.SITE_TITLE,
siteSubtitle: settings.subtitle ?? env.SITE_SUBTITLE,
seo: {
title: settings.seo?.title || settings.title || env.SITE_TITLE || 'Gallery',
description: settings.seo?.description || settings.subtitle || env.SITE_SUBTITLE || 'A curated photography portfolio',
noIndex: settings.seo?.noIndex === true,
noFollow: settings.seo?.noFollow === true,
},
heroImages: gallery.hero
? (Array.isArray(gallery.hero) ? gallery.hero : [gallery.hero]).map((id) =>
validateUuid(id, 'gallery.yaml hero'),