目錄
①對于更成熟的網站,簡單的index.html的入口文件的seo已經無法滿足,需要在商品詳情不同商品被搜索時賦予不同的title和description。
②通過設置站點所有頁面都新增Canonical標簽,指定規范鏈接地址給谷歌并規避聯盟的重復內容頁面。
③產品結構化微數據
①對于更成熟的網站,簡單的index.html的入口文件的seo已經無法滿足,需要在商品詳情不同商品被搜索時賦予不同的title和description。
<!-- src/views/main/goods/goods-details/goods-details.vue -->
<template><!-- 模板部分保持不變 -->
</template><script>
// ... 其他導入保持不變export default {name: "goods-details",// ... 其他配置保持不變data() {return {// ... 其他數據保持不變originalMetaTags: {} // 保存原始meta標簽}},// ... 其他配置保持不變methods: {// 設置產品頁面的SEO信息setProductSEO(dataInfo) {if (!dataInfo) return;// 保存原始的meta標簽信息(只在第一次調用時保存)if (!this.originalMetaTags.title) {this.originalMetaTags.title = document.title;this.originalMetaTags.description = this.getMetaContent('name', 'description');this.originalMetaTags['og:title'] = this.getMetaContent('property', 'og:title');this.originalMetaTags['og:description'] = this.getMetaContent('property', 'og:description');this.originalMetaTags['twitter:title'] = this.getMetaContent('name', 'twitter:title');this.originalMetaTags['twitter:description'] = this.getMetaContent('name', 'twitter:description');}// 獲取產品標題const productTitle = dataInfo.subject || '';// 設置新的頁面標題const newTitle = `Buy ${productTitle} | Hipobuy`;document.title = newTitle;// 設置新的meta descriptionconst newDescription = `Find the perfect ${productTitle} on Taobao or 1688? Hipobuy is your trusted China shopping agent. Start shopping now!`;// 更新所有meta標簽this.updateMetaTag('name', 'description', newDescription);this.updateMetaTag('property', 'og:title', newTitle);this.updateMetaTag('property', 'og:description', newDescription);this.updateMetaTag('name', 'twitter:title', newTitle);this.updateMetaTag('name', 'twitter:description', newDescription);},// 獲取meta標簽內容的輔助方法getMetaContent(attribute, value) {const metaTag = document.querySelector(`meta[${attribute}="${value}"]`);return metaTag ? metaTag.getAttribute('content') : '';},// 更新meta property標簽updateMetaProperty(property, content) {let metaTag = document.querySelector(`meta[property="${property}"]`);if (metaTag) {metaTag.setAttribute('content', content);} else {metaTag = document.createElement('meta');metaTag.setAttribute('property', property);metaTag.content = content;document.head.appendChild(metaTag);}},// 更新meta標簽的通用方法updateMetaTag(attribute, value, content) {let metaTag = document.querySelector(`meta[${attribute}="${value}"]`);if (metaTag) {metaTag.setAttribute('content', content);} else {metaTag = document.createElement('meta');metaTag.setAttribute(attribute, value);metaTag.content = content;document.head.appendChild(metaTag);}},// 完整恢復原始meta標簽(頁面銷毀時調用)restoreOriginalMetaTags() {// 恢復titleif (this.originalMetaTags.title) {document.title = this.originalMetaTags.title;}// 恢復descriptionif (this.originalMetaTags.description !== undefined) {const descriptionMeta = document.querySelector('meta[name="description"]');if (descriptionMeta) {if (this.originalMetaTags.description) {descriptionMeta.setAttribute('content', this.originalMetaTags.description);} else {descriptionMeta.remove(); // 如果原來沒有,就刪除添加的}} else if (this.originalMetaTags.description) {// 如果原來有但現在沒有,就重新創建const meta = document.createElement('meta');meta.name = 'description';meta.content = this.originalMetaTags.description;document.head.appendChild(meta);}}// 恢復og:titlethis.restoreMetaTag('property', 'og:title', this.originalMetaTags['og:title']);// 恢復og:descriptionthis.restoreMetaTag('property', 'og:description', this.originalMetaTags['og:description']);// 恢復twitter:titlethis.restoreMetaTag('name', 'twitter:title', this.originalMetaTags['twitter:title']);// 恢復twitter:descriptionthis.restoreMetaTag('name', 'twitter:description', this.originalMetaTags['twitter:description']);},// 恢復單個meta標簽的輔助方法restoreMetaTag(attribute, value, originalContent) {const metaTag = document.querySelector(`meta[${attribute}="${value}"]`);if (metaTag) {if (originalContent) {metaTag.setAttribute('content', originalContent);} else {metaTag.remove(); // 如果原來沒有,刪除添加的標簽}} else if (originalContent) { // 如果原來有但現在沒有,重新創建const meta = document.createElement('meta');meta.setAttribute(attribute, value);meta.content = originalContent;document.head.appendChild(meta);}},getQueryProductDetail(force) {this.loading = true;queryProductDetail({spuNo: this.id,refresh: !!force ? 1 : 0,channel: this.channel,activityCode: this.activityCode}).then((res) => {if (res.code == 1010) {this.loading = false;this.$refs.tipsPopRef.open(res.message);} else if (res.code == 200) {if (res.result.productGrayscale) {this.$refs.grayPopRef.open(res.result.productGrayscaleName)}this.loading = false;this.dataInfo = res.result;this.dataInfo.description = this.dataInfo.description.replace(/\s*href="[^"]*"/gi, '');// 設置產品SEO信息——getQueryProductDetail此請求只添加了這個設置,其余不需改動this.setProductSEO(this.dataInfo);if (this.dataInfo.channel == 'TAOBAO') {detailPointBtn(this.$route.params.spuNo, 'EXP', 'buy_icon');detailPointBtn(this.$route.params.spuNo, 'EXP', 'addcart_icon');}if (res.result.spuActivityVO) {if (this.$analytics) {this.$analytics.logEvent('pt_1001');}}}}).catch(() => {this.loading = false;})},// ... 其他方法保持不變},beforeDestroy() {// 恢復原始的meta標簽this.restoreOriginalMetaTags();}
}
</script><!-- 樣式部分保持不變 -->
本地環境自測以html的標簽為準即可;
②通過設置站點所有頁面都新增Canonical標簽,指定規范鏈接地址給谷歌并規避聯盟的重復內容頁面。
處理帶有URL參數的頁面
場景:電商網站的產品頁面可能會因為不同的篩選條件(如顏色、尺寸、排序方式等)而生成帶有不同參數的URL。
示例:https://example.com/product?id=123&color=blue
https://example.com/product?id=123&size=large
解決方案:在所有帶參數的頁面中,使用Canonical標簽指向主要的產品頁面,如:
<link rel="canonical" href="https://example.com/product?id=123" />
分頁內容
場景:文章列表或產品列表頁面經常會分頁(如第1頁、第2頁)
示例:https://example.com/blog?page=1
https://example.com/blog?page=2
解決方案:可以讓每個分頁頁面的Canonical標簽指向第一頁,或者讓每頁的Canonical標簽指向自身。<link rel="canonical" href="https://example.com/blog?page=1" />
內容分發與跟蹤參數
場景:網站在不同渠道(如社交媒體)分發內容,帶有UTM等跟蹤參數
示例:https://example.com/blog/post?utm_source=facebook
解決方案:使用Canonical標簽指向無參數的主要URL.
<link rel="canonical" href="https://example.com/blog/post" />
下面是我的實踐實例動態設置所有頁面:
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en-US">
<head><!-- 其他head內容保持不變 --><!-- 添加基礎canonical標簽 --><link rel="canonical" href="https://hipobuy.com/" /><!-- 其他head內容保持不變 -->
</head>
<body><div id="app"></div>
</body>
</html>
// 在 src/router/index.js 中添加
router.afterEach((to) => {// 延遲執行確保DOM已更新setTimeout(() => {const canonicalLink = document.querySelector('link[rel="canonical"]');if (canonicalLink) {// 獲取基礎URL,移除查詢參數和hashconst baseUrl = window.location.origin;const pathname = to.path;const canonicalUrl = baseUrl + pathname;canonicalLink.href = canonicalUrl;}}, 100);
});
特殊情況:電商網站中如果路由配置了參數可以用這種配置,不過具體在控制臺查看link標簽,我發現以上路由設置已經足夠,可以把參數也帶進canonical標簽,如果不行參考以下:
<!-- 在 src/views/main/goods/goods-details/goods-details.vue 的 methods 中添加 -->
methods: {// ... 其他方法保持不變// 添加這個方法來設置產品頁的canonicalsetProductCanonical() {const canonicalLink = document.querySelector('link[rel="canonical"]');if (canonicalLink) {// 構建產品頁的標準canonical URLconst channelMap = {"0": "1688","1": "taobao","2": "weidian","3": "jd"};const channelName = channelMap[this.channel] || this.channel.toLowerCase();const canonicalUrl = `https://hipobuy.com/product/${channelName}/${this.id}`;canonicalLink.href = canonicalUrl;}},// 在 getQueryProductDetail 方法的成功回調中調用getQueryProductDetail(force) {// ... 其他代碼保持不變queryProductDetail({// ... 參數保持不變}).then((res) => {// ... 其他成功處理保持不變if (res.code == 200) {// ... 其他處理保持不變// 設置產品SEO信息this.setProductSEO(this.dataInfo);// 添加這一行來設置產品頁canonicalthis.setProductCanonical();// ... 其他處理保持不變}}).catch(() => {this.loading = false;})}
}
③產品結構化微數據
這是代碼效果示例,不可以直接添加在html:
<html><head><title>Executive Anvil</title><script type="application/ld+json">{"@context": "https://schema.org/","@type": "Product","name": "Executive Anvil","image": ["https://example.com/photos/1x1/photo.jpg","https://example.com/photos/4x3/photo.jpg","https://example.com/photos/16x9/photo.jpg"],"description": "Sleeker than ACME's Classic Anvil, the Executive Anvil is perfect for the business traveler looking for something to drop from a height.","sku": "0446310786","mpn": "925872","brand": {"@type": "Brand","name": "ACME"},"review": {"@type": "Review","reviewRating": {"@type": "Rating","ratingValue": 4,"bestRating": 5},"author": {"@type": "Person","name": "Fred Benson"}},"aggregateRating": {"@type": "AggregateRating","ratingValue": 4.4,"reviewCount": 89},"offers": {"@type": "AggregateOffer","offerCount": 5,"lowPrice": 119.99,"highPrice": 199.99,"priceCurrency": "USD"}}</script></head><body></body>
</html>
以下才是js效果代碼:
<!-- src/views/main/goods/goods-details/goods-details.vue -->
<script>
export default {name: "goods-details",// ... 其他代碼保持不變data() {return {// ... 其他數據保持不變structuredDataScript: null // 保存結構化數據script元素}},methods: {// 生成并插入結構化數據generateStructuredData(dataInfo) {if (!dataInfo) return;// 移除已存在的結構化數據this.removeStructuredData();// 構建基礎產品信息const structuredData = {"@context": "https://schema.org/","@type": "Product","name": dataInfo.subject || '',"image": this.getProductImages(dataInfo),"description": this.getProductDescription(dataInfo),"sku": dataInfo.spuNo || '',"mpn": dataInfo.spuNo || dataInfo.id || '',"brand": this.getProductBrand(dataInfo),"offers": this.getProductOffers(dataInfo)};// 添加評分信息const ratingInfo = this.getProductRating(dataInfo);if (ratingInfo) {structuredData.aggregateRating = ratingInfo;}// 添加評論信息(如果有)const reviews = this.getProductReviews(dataInfo);if (reviews && reviews.length > 0) {structuredData.review = reviews;}// 創建script標簽并插入到head中this.structuredDataScript = document.createElement('script');this.structuredDataScript.type = 'application/ld+json';this.structuredDataScript.textContent = JSON.stringify(structuredData, null, 2);// 添加標識便于清理this.structuredDataScript.setAttribute('data-structured-data', 'product');document.head.appendChild(this.structuredDataScript);},// 獲取產品圖片列表getProductImages(dataInfo) {const images = [];// 主圖if (dataInfo.mainImg) {images.push(dataInfo.mainImg);}// 圖片列表if (dataInfo.imageList && Array.isArray(dataInfo.imageList)) {images.push(...dataInfo.imageList);}// 去重return [...new Set(images)].slice(0, 10); // 最多10張圖片},// 獲取產品描述getProductDescription(dataInfo) {if (!dataInfo.description) return '';// 移除HTML標簽const tmp = document.createElement('div');tmp.innerHTML = dataInfo.description;return tmp.textContent || tmp.innerText || '';},// 獲取品牌信息getProductBrand(dataInfo) {const channelNames = {"0": "1688","1": "Taobao","2": "Weidian","3": "JD","1688": "1688","TAOBAO": "Taobao","WEIDIAN": "Weidian","JD": "JD"};const brandName = dataInfo.brandName ||channelNames[dataInfo.channel] ||channelNames[this.channel] ||"Chinese E-commerce Platform";return {"@type": "Brand","name": brandName};},// 獲取產品報價信息getProductOffers(dataInfo) {const price = this.getProductPrice(dataInfo);const currency = dataInfo.currencyCode || "CNY";const availability = this.getAvailabilityStatus(dataInfo);return {"@type": "Offer","url": window.location.href,"priceCurrency": currency,"price": price,"availability": availability,"priceValidUntil": this.getPriceValidUntil(),"seller": {"@type": "Organization","name": "Hipobuy"}};},// 獲取產品價格getProductPrice(dataInfo) {if (dataInfo.price) {return dataInfo.priceCNY ? dataInfo.priceCNY : dataInfo.price;} else if (dataInfo.saleInfo) {if (dataInfo.saleInfo.priceRangeList && dataInfo.saleInfo.priceRangeList.length > 0) {return dataInfo.saleInfo.priceRangeList[0].priceCNY ?dataInfo.saleInfo.priceRangeList[0].priceCNY :dataInfo.saleInfo.priceRangeList[0].price;} else if (dataInfo.saleInfo.priceRanges && dataInfo.saleInfo.priceRanges.length > 0) {return dataInfo.saleInfo.priceRanges[0].priceCNY ?dataInfo.saleInfo.priceRanges[0].priceCNY :dataInfo.saleInfo.priceRanges[0].price;}}return "0";},// 獲取產品評分信息getProductRating(dataInfo) {if (dataInfo.score || dataInfo.commentNum) {return {"@type": "AggregateRating","ratingValue": dataInfo.score || 0,"reviewCount": dataInfo.commentNum || 0,"bestRating": 5,"worstRating": 1};}return null;},// 獲取產品評論信息getProductReviews(dataInfo) {// 如果有具體的評論數據,可以在這里處理// 目前返回空數組return [];},// 獲取庫存狀態getAvailabilityStatus(dataInfo) {// 根據庫存信息判斷可用性if (dataInfo.stock !== undefined) {return dataInfo.stock > 0 ? "https://schema.org/InStock" : "https://schema.org/OutOfStock";}// 根據狀態判斷if (dataInfo.status !== undefined) {switch (dataInfo.status) {case 'ON_SALE':case 'AVAILABLE':return "https://schema.org/InStock";case 'SOLD_OUT':case 'OFF_SALE':return "https://schema.org/OutOfStock";default:return "https://schema.org/InStock";}}return "https://schema.org/InStock"; // 默認有庫存},// 獲取價格有效期getPriceValidUntil() {const date = new Date();date.setMonth(date.getMonth() + 3); // 價格有效期3個月return date.toISOString().split('T')[0];},// 移除結構化數據removeStructuredData() {if (this.structuredDataScript) {document.head.removeChild(this.structuredDataScript);this.structuredDataScript = null;}},},// 組件銷毀時移除結構化數據beforeDestroy() {this.removeStructuredData(); // 移除結構化數據}
}
</script>
// 生成結構化數據---------在調用商品詳情的接口內調用并傳遞res.dataInfothis.generateStructuredData(this.dataInfo);
執行完之后可以在控制臺head標簽內查找是否展現: