feat(seo): 结构化数据/每页元数据/列表页metadata/hreflang修正/OG分享图
- 详情页注入 Article+BreadcrumbList JSON-LD,工具页 SoftwareApplication - 课程/技能/文章/练习 动态 generateMetadata(canonical/OG) - 新增 skills/contents/practices/prompts/models 列表页 metadata - 修正 hreflang(删除不存在的/en),用 next/og 生成 OG 分享图
This commit is contained in:
@@ -1,10 +1,57 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { API_BASE, SITE_URL } from '@/lib/config';
|
||||
import { articleJsonLd, breadcrumbJsonLd, absoluteUrl } from '@/lib/seo';
|
||||
import SkillDetailClient from './client';
|
||||
|
||||
async function getSkill(id: string) {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/skills/${id}`, { next: { revalidate: 3600 } });
|
||||
if (!res.ok) return null;
|
||||
return res.json();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function generateStaticParams() {
|
||||
const skillIds = ['general-chat', 'coding', 'writing', 'study', 'english', 'prompt-engineering', 'data-analysis', 'career'];
|
||||
return skillIds.map(id => ({ id }));
|
||||
return skillIds.map((id) => ({ id }));
|
||||
}
|
||||
|
||||
export default function SkillDetailPage() {
|
||||
return <SkillDetailClient />;
|
||||
export async function generateMetadata({ params }: { params: { id: string } }): Promise<Metadata> {
|
||||
const skill = await getSkill(params.id);
|
||||
if (!skill) return { title: '技能未找到' };
|
||||
const title = skill.name;
|
||||
const description = skill.description || `${skill.name} - 宇之然 AI 技能,免费学习与实践`;
|
||||
const url = absoluteUrl(`/skills/${skill.id}`);
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
alternates: { canonical: `/skills/${skill.id}` },
|
||||
openGraph: { type: 'article', title, description, url },
|
||||
};
|
||||
}
|
||||
|
||||
export default async function SkillDetailPage({ params }: { params: { id: string } }) {
|
||||
const skill = await getSkill(params.id);
|
||||
const url = absoluteUrl(`/skills/${params.id}`);
|
||||
const jsonLd = skill
|
||||
? [
|
||||
articleJsonLd({ headline: skill.name, description: skill.description || undefined, url }),
|
||||
breadcrumbJsonLd([
|
||||
{ name: '首页', url: SITE_URL },
|
||||
{ name: '技能库', url: absoluteUrl('/skills') },
|
||||
{ name: skill.name, url },
|
||||
]),
|
||||
]
|
||||
: null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{jsonLd && (
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
|
||||
)}
|
||||
<SkillDetailClient />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user