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

View File

@@ -6,6 +6,14 @@
title: "My Portfolio" title: "My Portfolio"
subtitle: "A visual journal" 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 # Show EXIF metadata (camera, lens, ISO) on photo grid hover
exifOnHover: true exifOnHover: true

View File

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