From c49fa9feee3503c3ace625a6331b767c39c689e4 Mon Sep 17 00:00:00 2001 From: jingrow Date: Fri, 19 Dec 2025 22:25:58 +0800 Subject: [PATCH] feat: implement SEO optimization with relative URLs and structured data - Add SEO meta tags (title, description, keywords, robots) - Add Open Graph and Twitter Card meta tags - Add JSON-LD structured data (WebPage schema) - Use relative URLs for og:url, og:image, and canonical links to avoid hardcoded localhost addresses in prerendered HTML - Support internationalized keywords with English defaults and Chinese translations - Optimize for Chinese search engines (Baidu, 360, Sogou) This ensures SEO metadata is properly captured in prerendered static HTML files without hardcoding development server URLs. --- .../frontend/scripts/vite-plugin-prerender.ts | 2 +- apps/jingrow/frontend/src/app/router/index.ts | 9 ++ apps/jingrow/frontend/src/locales/zh-CN.json | 2 + .../frontend/src/shared/composables/useSEO.ts | 129 ++++++++++++++++++ .../remove_background/remove_background.vue | 11 ++ 5 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 apps/jingrow/frontend/src/shared/composables/useSEO.ts diff --git a/apps/jingrow/frontend/scripts/vite-plugin-prerender.ts b/apps/jingrow/frontend/scripts/vite-plugin-prerender.ts index 694811b..4726e6f 100644 --- a/apps/jingrow/frontend/scripts/vite-plugin-prerender.ts +++ b/apps/jingrow/frontend/scripts/vite-plugin-prerender.ts @@ -87,7 +87,7 @@ export function vitePluginPrerender(options: PrerenderPluginOptions = {}): Plugi timeout: rendererOptions.timeout || 30000, waitUntil: rendererOptions.waitUntil || 'networkidle0', // 渲染选项:等待页面加载完成 - renderAfterTime: 2000, // 等待 2 秒确保内容加载完成 + renderAfterTime: 3000, // 等待 3 秒确保Vue组件挂载和SEO标签注入完成 // 注入一些配置,确保页面正确渲染 inject: { __PRERENDER__: true diff --git a/apps/jingrow/frontend/src/app/router/index.ts b/apps/jingrow/frontend/src/app/router/index.ts index c686531..78bcec6 100644 --- a/apps/jingrow/frontend/src/app/router/index.ts +++ b/apps/jingrow/frontend/src/app/router/index.ts @@ -204,6 +204,9 @@ const router = createRouter({ }) router.beforeEach(async (to, _from, next) => { + // 检测预渲染环境 + const isPrerendering = typeof window !== 'undefined' && navigator.userAgent.includes('HeadlessChrome') + const authStore = useAuthStore() if (!authStore.isAuthenticated) { @@ -254,6 +257,12 @@ router.beforeEach(async (to, _from, next) => { } } + // 预渲染时跳过认证检查 + if (isPrerendering) { + next() + return + } + if (to.meta.requiresAuth && !authStore.isLoggedIn) { next('/login') } else if ((to.path === '/login' || to.path === '/signup') && authStore.isLoggedIn) { diff --git a/apps/jingrow/frontend/src/locales/zh-CN.json b/apps/jingrow/frontend/src/locales/zh-CN.json index c38e22a..6c5c9b5 100644 --- a/apps/jingrow/frontend/src/locales/zh-CN.json +++ b/apps/jingrow/frontend/src/locales/zh-CN.json @@ -1112,6 +1112,8 @@ "Route not found: ": "路由未找到:", "Remove Background": "图片去背景", "Remove background from images using AI technology": "使用AI技术去除图片背景", + "Remove background from images using AI technology. Free online tool to remove image backgrounds instantly. Supports JPG, PNG, WebP formats.": "使用AI技术去除图片背景。免费在线工具,即时去除图片背景。支持 JPG、PNG、WebP 格式。", + "remove background, AI background removal, online background remover, image processing, background removal tool, transparent background, free tool, JPG background removal, PNG background removal": "图片去背景,AI去背景,在线去背景,图片处理,背景移除,透明背景,免费工具,JPG去背景,PNG去背景", "Upload Image": "上传图片", "Drag and drop your image here, or click to browse": "拖拽图片到此处,或点击浏览", "Supports JPG, PNG, WebP formats": "支持 JPG、PNG、WebP 格式", diff --git a/apps/jingrow/frontend/src/shared/composables/useSEO.ts b/apps/jingrow/frontend/src/shared/composables/useSEO.ts new file mode 100644 index 0000000..3554bd6 --- /dev/null +++ b/apps/jingrow/frontend/src/shared/composables/useSEO.ts @@ -0,0 +1,129 @@ +/** + * SEO工具函数 - 符合国内搜索引擎和未来趋势 + * 在组件中直接定义SEO信息,立即同步设置到HTML + */ +const DEFAULT_SITE_NAME = 'Jingrow' +const DEFAULT_OG_IMAGE = '/logo.svg' + +function setMetaTag(name: string, content: string, attribute: 'name' | 'property' = 'name') { + if (!content || typeof document === 'undefined') return + + const selector = `meta[${attribute}="${name}"]` + let tag = document.querySelector(selector) as HTMLMetaElement + + if (!tag) { + tag = document.createElement('meta') + tag.setAttribute(attribute, name) + document.head.appendChild(tag) + } + + tag.setAttribute('content', content) +} + +function setLinkTag(rel: string, href: string) { + if (!href || typeof document === 'undefined') return + + const selector = `link[rel="${rel}"]` + let tag = document.querySelector(selector) as HTMLLinkElement + + if (!tag) { + tag = document.createElement('link') + tag.setAttribute('rel', rel) + document.head.appendChild(tag) + } + + tag.setAttribute('href', href) +} + +function setJSONLD(data: object) { + if (typeof document === 'undefined') return + + // 移除旧的 JSON-LD + const existingScript = document.querySelector('script[type="application/ld+json"]') + if (existingScript) { + existingScript.remove() + } + + // 添加新的 JSON-LD + const script = document.createElement('script') + script.type = 'application/ld+json' + script.textContent = JSON.stringify(data, null, 2) + document.head.appendChild(script) +} + +/** + * 设置SEO标签 - 立即同步执行 + * 符合国内搜索引擎(百度、360、搜狗)和未来趋势 + * @param data SEO数据 + */ +export function useSEO(data: { + title?: string + description?: string + keywords?: string + image?: string + url?: string + robots?: string +}) { + if (typeof window === 'undefined' || typeof document === 'undefined') return + + const pathname = window.location.pathname + const url = data.url || pathname + const image = data.image || DEFAULT_OG_IMAGE + const title = data.title ? `${data.title} - ${DEFAULT_SITE_NAME}` : DEFAULT_SITE_NAME + + // Title - 立即设置(百度推荐:前20字核心信息+品牌词) + if (data.title) document.title = title + + // Meta description - 立即设置(百度推荐:150字符以内) + if (data.description) setMetaTag('description', data.description) + + // Keywords - 国内搜索引擎仍在使用 + if (data.keywords) setMetaTag('keywords', data.keywords) + + // Robots - 控制爬虫行为 + if (data.robots) { + setMetaTag('robots', data.robots) + } else { + // 默认允许索引 + setMetaTag('robots', 'index,follow') + } + + // Open Graph - 立即设置 + if (data.title) setMetaTag('og:title', data.title, 'property') + if (data.description) setMetaTag('og:description', data.description, 'property') + setMetaTag('og:image', image, 'property') + setMetaTag('og:url', url, 'property') + setMetaTag('og:type', 'website', 'property') + setMetaTag('og:site_name', DEFAULT_SITE_NAME, 'property') + setMetaTag('og:locale', 'zh_CN', 'property') + + // Twitter Card - 立即设置 + setMetaTag('twitter:card', 'summary_large_image') + if (data.title) setMetaTag('twitter:title', data.title) + if (data.description) setMetaTag('twitter:description', data.description) + setMetaTag('twitter:image', image) + + // Canonical - 立即设置 + setLinkTag('canonical', url) + + // 结构化数据(JSON-LD) + if (data.title && data.description) { + const jsonLd = { + '@context': 'https://schema.org', + '@type': 'WebPage', + name: data.title, + description: data.description, + url: url, + publisher: { + '@type': 'Organization', + name: DEFAULT_SITE_NAME, + logo: { + '@type': 'ImageObject', + url: image + } + } + } + + setJSONLD(jsonLd) + } +} diff --git a/apps/jingrow/frontend/src/views/tools/remove_background/remove_background.vue b/apps/jingrow/frontend/src/views/tools/remove_background/remove_background.vue index 27c5a50..2f99c28 100644 --- a/apps/jingrow/frontend/src/views/tools/remove_background/remove_background.vue +++ b/apps/jingrow/frontend/src/views/tools/remove_background/remove_background.vue @@ -185,13 +185,24 @@