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:
parent
bbde769b49
commit
c49fa9feee
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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 格式",
|
||||
|
||||
129
apps/jingrow/frontend/src/shared/composables/useSEO.ts
Normal file
129
apps/jingrow/frontend/src/shared/composables/useSEO.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user