feat: add SEO fields to settings configuration
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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'),
|
||||
|
||||
Reference in New Issue
Block a user