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 { 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();
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
Reference in New Issue
Block a user