feat(seo): improve SEO configuration and enable SSR
- Enable `ssr: true` in `nuxt.config.ts` for server-side rendering of meta tags. - Implement `useSeoMeta` in `events/.vue` with fallback logic for Open Graph and Twitter cards. - Update `content.config.ts` to use `asSeoCollection` for the news collection. - Migrate event markdown frontmatter to use standardized SEO fields.
This commit is contained in:
@@ -7,9 +7,13 @@
|
||||
<!-- 卡片装饰边框(浅色渐变) -->
|
||||
<!-- <div class="absolute inset-0 bg-linear-to-r from-blue-100 to-purple-100 rounded-2xl blur-sm"></div> -->
|
||||
|
||||
<div class="relative rounded-xl border border-gray-200 shadow-xl overflow-hidden">
|
||||
<div
|
||||
class="relative rounded-xl border border-gray-200 shadow-xl overflow-hidden"
|
||||
>
|
||||
<!-- 顶部装饰条(明亮渐变) -->
|
||||
<div class="h-1 bg-linear-to-r from-blue-400 via-purple-400 to-cyan-400"></div>
|
||||
<div
|
||||
class="h-1 bg-linear-to-r from-blue-400 via-purple-400 to-cyan-400"
|
||||
></div>
|
||||
|
||||
<div class="p-8 sm:p-10 lg:p-12">
|
||||
<!-- 内容渲染器 -->
|
||||
@@ -17,16 +21,32 @@
|
||||
<ContentRenderer :value="event ?? {}">
|
||||
<template #empty>
|
||||
<div class="text-center py-16">
|
||||
<div class="inline-flex items-center justify-center w-16 h-16 bg-blue-100 rounded-full mb-4">
|
||||
<svg class="w-8 h-8 text-blue-500 animate-spin" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
|
||||
</circle>
|
||||
<path class="opacity-75" fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
|
||||
</path>
|
||||
<div
|
||||
class="inline-flex items-center justify-center w-16 h-16 bg-blue-100 rounded-full mb-4"
|
||||
>
|
||||
<svg
|
||||
class="w-8 h-8 text-blue-500 animate-spin"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="text-gray-700 text-lg font-medium">内容加载中...</p>
|
||||
<p class="text-gray-700 text-lg font-medium">
|
||||
内容加载中...
|
||||
</p>
|
||||
<p class="text-gray-400 text-sm mt-2">请稍等片刻</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -41,16 +61,43 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const route = useRoute()
|
||||
const { data: event } = await useAsyncData('event-detail', () =>
|
||||
queryCollection('events')
|
||||
.path(`/events/${route.params.slug}`)
|
||||
.first()
|
||||
)
|
||||
const route = useRoute();
|
||||
const { data: event } = await useAsyncData("event-detail", () =>
|
||||
queryCollection("events").path(`/events/${route.params.slug}`).first()
|
||||
);
|
||||
|
||||
useHead({
|
||||
title: event.value?.title
|
||||
})
|
||||
if (event.value) {
|
||||
// 1. 确定图片:优先用 ogImage,没有就用 cover
|
||||
const shareImage = event.value.ogImage || event.value.cover;
|
||||
|
||||
// 2. 确定标题和描述:优先用 seoTitle,没有就用 title
|
||||
const shareTitle = event.value.seoTitle || event.value.title;
|
||||
const shareDesc = event.value.seoDescription || event.value.description;
|
||||
|
||||
// 3. 注入 SEO
|
||||
useSeoMeta({
|
||||
// 基础
|
||||
title: shareTitle,
|
||||
description: shareDesc,
|
||||
|
||||
// Open Graph (Facebook / WhatsApp)
|
||||
ogTitle: shareTitle,
|
||||
ogDescription: shareDesc,
|
||||
ogImage: shareImage,
|
||||
ogType: "article",
|
||||
|
||||
// Twitter Card
|
||||
twitterCard: "summary_large_image",
|
||||
twitterTitle: shareTitle,
|
||||
twitterDescription: shareDesc,
|
||||
twitterImage: shareImage,
|
||||
});
|
||||
|
||||
// 如果你用了 nuxt-og-image 模块生成动态图
|
||||
if (shareImage) {
|
||||
defineOgImage({ url: shareImage });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { defineContentConfig, defineCollection, z } from "@nuxt/content";
|
||||
import { asSeoCollection } from "@nuxtjs/seo/content";
|
||||
|
||||
export default defineContentConfig({
|
||||
collections: {
|
||||
@@ -12,11 +13,12 @@ export default defineContentConfig({
|
||||
date: z.coerce.date(),
|
||||
location: z.string(),
|
||||
cover: z.string().url(),
|
||||
draft: z.boolean().optional().default(false)
|
||||
draft: z.boolean().optional().default(false),
|
||||
}),
|
||||
}),
|
||||
// 新闻集合
|
||||
news: defineCollection({
|
||||
news: defineCollection(
|
||||
asSeoCollection({
|
||||
type: "page",
|
||||
source: "news/*.md",
|
||||
schema: z.object({
|
||||
@@ -32,9 +34,10 @@ export default defineContentConfig({
|
||||
seoTitle: z.string().optional(),
|
||||
seoDescription: z.string().optional(),
|
||||
ogImage: z.string().optional(),
|
||||
draft: z.boolean().optional().default(false)
|
||||
}),
|
||||
draft: z.boolean().optional().default(false),
|
||||
}),
|
||||
})
|
||||
),
|
||||
// 名人堂
|
||||
hallOfFames: defineCollection({
|
||||
type: "page",
|
||||
|
||||
@@ -17,35 +17,19 @@ keywords:
|
||||
- 刘连升老师
|
||||
- 永中校友会
|
||||
|
||||
# Open Graph / Facebook
|
||||
og:
|
||||
title: "永平中学第 60 届毕业典礼|初中第 67 届毕业典礼"
|
||||
description: "2025 年永平中学毕业典礼隆重举行,包含师长致辞、奖学金颁发、荣休老师欢送,以及学生精彩演出等精彩环节。"
|
||||
image: "https://img.yphsalumni.org/i/2025/11/27/st6hzt.jpg"
|
||||
type: "article"
|
||||
# --- SEO 专用字段 (对应你的 Zod Schema) ---
|
||||
|
||||
# Twitter 卡片
|
||||
twitter:
|
||||
card: "summary_large_image"
|
||||
title: "永平中学第 60 届毕业典礼"
|
||||
description: "永平中学 2025 毕业典礼精彩回顾:致辞、演出、奖学金颁发与荣休教师表扬。"
|
||||
image: "https://img.yphsalumni.org/i/2025/11/27/st6hzt.jpg"
|
||||
# 对应 schema: seoTitle
|
||||
# 如果不填,代码里会默认使用 title
|
||||
seoTitle: "永平中学第 60 届毕业典礼|初中第 67 届毕业典礼"
|
||||
|
||||
# 文章结构化数据(可选,Nuxt SEO module 会自动识别)
|
||||
structuredData:
|
||||
"@type": "NewsArticle"
|
||||
headline: "永平中学第 60 届毕业典礼圆满举行"
|
||||
image: "https://img.yphsalumni.org/i/2025/11/27/st6hzt.jpg"
|
||||
datePublished: "2025-11-15"
|
||||
author:
|
||||
"@type": "Organization"
|
||||
name: "永平中学校友会"
|
||||
publisher:
|
||||
"@type": "Organization"
|
||||
name: "永平中学"
|
||||
logo:
|
||||
"@type": "ImageObject"
|
||||
url: "/logo.png"
|
||||
# 对应 schema: seoDescription
|
||||
# 如果不填,代码里会默认使用 description
|
||||
seoDescription: "2025 年永平中学毕业典礼隆重举行,包含师长致辞、奖学金颁发、荣休老师欢送,以及学生精彩演出等精彩环节。"
|
||||
|
||||
# 对应 schema: ogImage
|
||||
# 只有当你想要分享的图片和封面图不一样时才填,否则代码里会默认用 cover
|
||||
ogImage: "https://img.yphsalumni.org/i/2025/11/27/st6hzt.jpg"
|
||||
---
|
||||
|
||||
# 永平中学高中第 60 届、初中第 67 届毕业典礼圆满举行
|
||||
|
||||
@@ -5,14 +5,15 @@ export default defineNuxtConfig({
|
||||
compatibilityDate: "2025-07-15",
|
||||
devtools: { enabled: true },
|
||||
modules: [
|
||||
"@nuxtjs/seo",
|
||||
"@nuxt/ui",
|
||||
"@nuxtjs/seo",
|
||||
"@nuxt/content",
|
||||
"@nuxt/image",
|
||||
"reka-ui/nuxt",
|
||||
"@nuxtjs/robots",
|
||||
"@nuxtjs/sitemap",
|
||||
],
|
||||
ssr: true,
|
||||
css: ["~/assets/css/main.css"],
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
|
||||
Reference in New Issue
Block a user