diff --git a/apps/spa/src/components/ThemeInjector.tsx b/apps/spa/src/components/ThemeInjector.tsx index ef9256b..7793c40 100644 --- a/apps/spa/src/components/ThemeInjector.tsx +++ b/apps/spa/src/components/ThemeInjector.tsx @@ -1,9 +1,12 @@ import { useEffect } from 'react' +import { useTranslation } from 'react-i18next' import { API_BASE } from '@homaroma/types' export default function ThemeInjector() { + const { i18n } = useTranslation() + useEffect(() => { - const lang = localStorage.getItem('app_lang') || 'en' + const lang = i18n.language || localStorage.getItem('app_lang') || 'en' fetch(`${API_BASE}/settings?lang=${lang}`) .then(res => res.ok ? res.json() : null) @@ -11,6 +14,52 @@ export default function ThemeInjector() { if (!settings) return const root = document.documentElement + // --- Global SEO & Document Title --- + const pageTitle = settings.defaultSeoTitle || settings.brandName + if (pageTitle) { + document.title = pageTitle + } + + if (settings.defaultSeoDescription) { + let metaDesc = document.querySelector('meta[name="description"]') + if (!metaDesc) { + metaDesc = document.createElement('meta') + metaDesc.setAttribute('name', 'description') + document.head.appendChild(metaDesc) + } + metaDesc.setAttribute('content', settings.defaultSeoDescription) + } + + if (pageTitle) { + let ogTitle = document.querySelector('meta[property="og:title"]') + if (!ogTitle) { + ogTitle = document.createElement('meta') + ogTitle.setAttribute('property', 'og:title') + document.head.appendChild(ogTitle) + } + ogTitle.setAttribute('content', pageTitle) + } + + if (settings.defaultSeoDescription) { + let ogDesc = document.querySelector('meta[property="og:description"]') + if (!ogDesc) { + ogDesc = document.createElement('meta') + ogDesc.setAttribute('property', 'og:description') + document.head.appendChild(ogDesc) + } + ogDesc.setAttribute('content', settings.defaultSeoDescription) + } + + if (settings.defaultOgImage) { + let ogImg = document.querySelector('meta[property="og:image"]') + if (!ogImg) { + ogImg = document.createElement('meta') + ogImg.setAttribute('property', 'og:image') + document.head.appendChild(ogImg) + } + ogImg.setAttribute('content', settings.defaultOgImage) + } + // --- Theme tokens --- const tokens = settings?.themeTokens if (tokens && typeof tokens === 'object') { @@ -48,8 +97,8 @@ export default function ThemeInjector() { root.style.setProperty('--scroll-reveal-style', style) root.style.setProperty('--scroll-reveal-duration', `${duration}ms`) }) - .catch(err => console.error('Failed to inject theme in SPA:', err)) - }, []) + .catch(err => console.error('Failed to inject theme/SEO in SPA:', err)) + }, [i18n.language]) return null } diff --git a/apps/website/src/app/[lang]/layout.tsx b/apps/website/src/app/[lang]/layout.tsx index a4e74c3..e8029b2 100644 --- a/apps/website/src/app/[lang]/layout.tsx +++ b/apps/website/src/app/[lang]/layout.tsx @@ -12,20 +12,21 @@ import type { CSSProperties } from 'react'; export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise { const { lang } = await params; const settings = await getStoreSettings(lang); - const brandName = settings?.brandName || ''; + const brandName = settings?.brandName || 'HomAroma'; + const defaultTitle = settings?.defaultSeoTitle || brandName; const description = settings?.defaultSeoDescription || settings?.footerDescription || 'Pure Origins, Artisan Craft, Zero Compromise'; const ogImage = settings?.defaultOgImage; return { title: { template: `%s | ${brandName}`, - default: brandName, + default: defaultTitle, }, description, metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'), openGraph: ogImage ? { - title: brandName, + title: defaultTitle, description, images: [ogImage], } diff --git a/apps/website/src/app/[lang]/page.tsx b/apps/website/src/app/[lang]/page.tsx index da2ef93..1c76299 100644 --- a/apps/website/src/app/[lang]/page.tsx +++ b/apps/website/src/app/[lang]/page.tsx @@ -15,10 +15,11 @@ export async function generateMetadata({ params }: Props): Promise { getStoreSettings(lang), ]) - const title = page?.seoTitle || page?.title || undefined + const title = page?.seoTitle || settings?.defaultSeoTitle || (page?.title && page.title !== 'Home' && page.title !== 'หน้าแรก' ? page.title : undefined) || settings?.brandName || undefined const description = page?.seoDescription || settings?.defaultSeoDescription || + settings?.footerDescription || (settings?.brandName ? `${settings.brandName} - Pure Origins, Artisan Craft` : 'Pure Origins, Artisan Craft') const ogImage = page?.ogImageUrl || settings?.defaultOgImage @@ -29,7 +30,7 @@ export async function generateMetadata({ params }: Props): Promise { lang, openGraph: ogImage ? { - title: page?.seoTitle || page?.title || settings?.brandName || undefined, + title, description, images: [ogImage], }