From 297000f208be40e7f4dd1889c42db2ba7538a347 Mon Sep 17 00:00:00 2001 From: typist Date: Wed, 29 Oct 2025 08:15:53 +0800 Subject: [PATCH] feat: integrate SEO hook for dynamic metadata management - Removed hardcoded canonical link from index.html. - Implemented useSEO hook in layout.tsx to dynamically update SEO metadata including title, description, and canonical URL based on the current route. - Added new use-seo.ts file containing the SEO hook logic for improved search engine optimization. --- index.html | 1 - src/hooks/use-seo.ts | 96 ++++++++++++++++++++++++++++++++++++++++++++ src/layout.tsx | 38 ++++++++++-------- 3 files changed, 118 insertions(+), 17 deletions(-) create mode 100644 src/hooks/use-seo.ts diff --git a/index.html b/index.html index f558672..fc0a251 100644 --- a/index.html +++ b/index.html @@ -13,7 +13,6 @@ - diff --git a/src/hooks/use-seo.ts b/src/hooks/use-seo.ts new file mode 100644 index 0000000..9588eb9 --- /dev/null +++ b/src/hooks/use-seo.ts @@ -0,0 +1,96 @@ +import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +interface UseSEOOptions { + title?: string; + description?: string; + baseUrl?: string; +} + +/** + * SEO Hook - 动态更新页面 SEO 元数据 + * + * @param options - SEO 配置选项 + * @param options.title - 页面标题(可选) + * @param options.description - 页面描述(可选) + * @param options.baseUrl - 网站基础 URL,默认为 https://litek.typist.cc + * + * @example + * ```tsx + * // 在组件中使用 + * useSEO({ + * title: 'UUID Generator', + * description: 'Free online UUID generator tool' + * }); + * ``` + */ +export const useSEO = (options: UseSEOOptions = {}) => { + const location = useLocation(); + const { + title, + description, + baseUrl = 'https://litek.typist.cc' + } = options; + + useEffect(() => { + // 构建当前页面的完整 URL + const canonicalUrl = `${baseUrl}${location.pathname}`; + + // 更新或创建 canonical 链接 + let canonical = document.querySelector('link[rel="canonical"]') as HTMLLinkElement; + if (!canonical) { + canonical = document.createElement('link'); + canonical.setAttribute('rel', 'canonical'); + document.head.appendChild(canonical); + } + canonical.setAttribute('href', canonicalUrl); + + // 更新页面标题 + if (title) { + document.title = `${title} - Lite Kit`; + } + + // 更新 meta description + if (description) { + let metaDescription = document.querySelector('meta[name="description"]') as HTMLMetaElement; + if (metaDescription) { + metaDescription.setAttribute('content', description); + } + } + + // 更新 Open Graph URL + let ogUrl = document.querySelector('meta[property="og:url"]') as HTMLMetaElement; + if (ogUrl) { + ogUrl.setAttribute('content', canonicalUrl); + } + + // 更新 Open Graph Title + if (title) { + let ogTitle = document.querySelector('meta[property="og:title"]') as HTMLMetaElement; + if (ogTitle) { + ogTitle.setAttribute('content', `${title} - Lite Kit`); + } + + // 更新 Twitter Card Title + let twitterTitle = document.querySelector('meta[name="twitter:title"]') as HTMLMetaElement; + if (twitterTitle) { + twitterTitle.setAttribute('content', `${title} - Lite Kit`); + } + } + + // 更新 Open Graph Description + if (description) { + let ogDescription = document.querySelector('meta[property="og:description"]') as HTMLMetaElement; + if (ogDescription) { + ogDescription.setAttribute('content', description); + } + + // 更新 Twitter Card Description + let twitterDescription = document.querySelector('meta[name="twitter:description"]') as HTMLMetaElement; + if (twitterDescription) { + twitterDescription.setAttribute('content', description); + } + } + }, [location.pathname, title, description, baseUrl]); +}; + diff --git a/src/layout.tsx b/src/layout.tsx index 726a539..16b1a0c 100644 --- a/src/layout.tsx +++ b/src/layout.tsx @@ -4,20 +4,26 @@ import { Outlet } from "react-router-dom"; import { ThemeProvider } from "@/components/theme/provider" import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar" import { AppSidebar } from "@/components/sidebar"; +import { useSEO } from "@/hooks/use-seo"; -export const Layout: FC = () => ( - - - -
- -
- -
-
-
-
-); \ No newline at end of file +export const Layout: FC = () => { + // 使用 SEO hook 自动更新 canonical URL 和其他 SEO 元数据 + useSEO(); + + return ( + + + +
+ +
+ +
+
+
+
+ ); +}; \ No newline at end of file