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.
This commit is contained in:
jingrow 2025-12-19 22:25:58 +08:00
parent bbde769b49
commit c49fa9feee
5 changed files with 152 additions and 1 deletions

View File

@ -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

View File

@ -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) {

View File

@ -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 格式",

View File

@ -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)
}
}

View File

@ -185,13 +185,24 @@
<script setup lang="ts">
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import { useMessage } from 'naive-ui'
import { useRoute } from 'vue-router'
import axios from 'axios'
import { t } from '@/shared/i18n'
import { get_session_api_headers } from '@/shared/api/auth'
import { useAuthStore } from '@/shared/stores/auth'
import { useSEO } from '@/shared/composables/useSEO'
const message = useMessage()
const authStore = useAuthStore()
const route = useRoute()
// SEO - HTML
// 360
useSEO({
title: t('Remove Background'),
description: t('Remove background from images using AI technology. Free online tool to remove image backgrounds instantly. Supports JPG, PNG, WebP formats.'),
keywords: t('remove background, AI background removal, online background remover, image processing, background removal tool, transparent background, free tool, JPG background removal, PNG background removal')
})
interface HistoryItem {
id: string