mirror of
https://github.com/MCKero6423/fuwari.git
synced 2026-10-02 03:15:35 +00:00
SEO 优化:补全 canonical、og:image、JSON-LD 及页面描述
Layout.astro:
- 新增 <link rel="canonical">,og:url / twitter:url 统一使用规范链接
- 新增 og:image / twitter:image:文章封面为绝对或 public URL 时使用封面,
否则回退站点 favicon-192 并将 twitter:card 从 summary_large_image
降级为 summary,避免声明大图卡片却无图
- banner 判断改用 typeof 收窄,兼容 404 页传入 banner={false} 的情况
posts/[...slug].astro (JSON-LD BlogPosting):
- 新增 dateModified(读取 frontmatter 的 updated 字段)
- 新增 image(仅绝对 URL 封面)和 mainEntityOfPage
- 移除遗留的 TODO 注释
页面 meta description:
- archive / friends / expenses / series 四个页面补充独立描述,
不再退化为用页面标题填充
astro.config.mjs:
- sitemap 增加 filter 排除 /archive/,与 robots.txt 的
Disallow: /archive/ 保持一致,消除收录信号矛盾
This commit is contained in:
1 parent
b35a071c6b
commit
6105577add
7 files changed
+42
-9
No files matched your search
+3
-1
@@ -102,7 +102,9 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
svelte(),
|
svelte(),
|
||||||
sitemap(),
|
sitemap({
|
||||||
|
filter: (page) => !page.includes("/archive/"),
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
markdown: {
|
markdown: {
|
||||||
remarkPlugins: [
|
remarkPlugins: [
|
||||||
|
|||||||
@@ -30,6 +30,21 @@ interface Props {
|
|||||||
|
|
||||||
let { title, banner, description, lang, setOGTypeArticle } = Astro.props;
|
let { title, banner, description, lang, setOGTypeArticle } = Astro.props;
|
||||||
|
|
||||||
|
// canonical URL
|
||||||
|
const canonicalUrl = new URL(Astro.url.pathname, Astro.site).href;
|
||||||
|
|
||||||
|
// social share image: use the page's own image if it's an absolute or public URL,
|
||||||
|
// otherwise fall back to the site favicon with a smaller "summary" card
|
||||||
|
let ogImage: string | undefined;
|
||||||
|
if (typeof banner === "string" && /^https?:\/\//.test(banner)) {
|
||||||
|
ogImage = banner;
|
||||||
|
} else if (typeof banner === "string" && banner.startsWith("/")) {
|
||||||
|
ogImage = new URL(url(banner), Astro.site).href;
|
||||||
|
}
|
||||||
|
const twitterCard = ogImage ? "summary_large_image" : "summary";
|
||||||
|
const ogImageUrl =
|
||||||
|
ogImage ?? new URL(url("/favicon/favicon-light-192.png"), Astro.site).href;
|
||||||
|
|
||||||
// apply a class to the body element to decide the height of the banner, only used for initial page load
|
// apply a class to the body element to decide the height of the banner, only used for initial page load
|
||||||
// Swup can update the body for each page visit, but it's after the page transition, causing a delay for banner height change
|
// Swup can update the body for each page visit, but it's after the page transition, causing a delay for banner height change
|
||||||
// so use Swup hooks instead to change the height immediately when a link is clicked
|
// so use Swup hooks instead to change the height immediately when a link is clicked
|
||||||
@@ -83,21 +98,24 @@ const bannerOffset =
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="description" content={description || pageTitle}>
|
<meta name="description" content={description || pageTitle}>
|
||||||
<meta name="author" content={profileConfig.name}>
|
<meta name="author" content={profileConfig.name}>
|
||||||
|
<link rel="canonical" href={canonicalUrl}>
|
||||||
|
|
||||||
<meta property="og:site_name" content={siteConfig.title}>
|
<meta property="og:site_name" content={siteConfig.title}>
|
||||||
<meta property="og:url" content={Astro.url}>
|
<meta property="og:url" content={canonicalUrl}>
|
||||||
<meta property="og:title" content={pageTitle}>
|
<meta property="og:title" content={pageTitle}>
|
||||||
<meta property="og:description" content={description || pageTitle}>
|
<meta property="og:description" content={description || pageTitle}>
|
||||||
|
<meta property="og:image" content={ogImageUrl}>
|
||||||
{setOGTypeArticle ? (
|
{setOGTypeArticle ? (
|
||||||
<meta property="og:type" content="article" />
|
<meta property="og:type" content="article" />
|
||||||
) : (
|
) : (
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<meta name="twitter:card" content={twitterCard}>
|
||||||
<meta property="twitter:url" content={Astro.url}>
|
<meta property="twitter:url" content={canonicalUrl}>
|
||||||
<meta name="twitter:title" content={pageTitle}>
|
<meta name="twitter:title" content={pageTitle}>
|
||||||
<meta name="twitter:description" content={description || pageTitle}>
|
<meta name="twitter:description" content={description || pageTitle}>
|
||||||
|
<meta name="twitter:image" content={ogImageUrl}>
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<meta name="generator" content={Astro.generator} />
|
<meta name="generator" content={Astro.generator} />
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ import ArchivePanel from "@components/ArchivePanel.svelte";
|
|||||||
import I18nKey from "@i18n/i18nKey";
|
import I18nKey from "@i18n/i18nKey";
|
||||||
import { i18n } from "@i18n/translation";
|
import { i18n } from "@i18n/translation";
|
||||||
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
import MainGridLayout from "@layouts/MainGridLayout.astro";
|
||||||
|
import { siteConfig } from "@/config";
|
||||||
import { getSortedPostsList } from "../utils/content-utils";
|
import { getSortedPostsList } from "../utils/content-utils";
|
||||||
|
|
||||||
const sortedPostsList = await getSortedPostsList();
|
const sortedPostsList = await getSortedPostsList();
|
||||||
---
|
---
|
||||||
|
|
||||||
<MainGridLayout title={i18n(I18nKey.archive)}>
|
<MainGridLayout title={i18n(I18nKey.archive)} description={`${siteConfig.title}的全部文章归档,按时间浏览所有博客文章。`}>
|
||||||
<ArchivePanel sortedPosts={sortedPostsList} client:only="svelte"></ArchivePanel>
|
<ArchivePanel sortedPosts={sortedPostsList} client:only="svelte"></ArchivePanel>
|
||||||
</MainGridLayout>
|
</MainGridLayout>
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ const historyData = [
|
|||||||
];
|
];
|
||||||
---
|
---
|
||||||
|
|
||||||
<MainGridLayout title="运行成本">
|
<MainGridLayout title="运行成本" description="本博客的运行成本明细,包括域名、CDN 等固定支出记录。">
|
||||||
<div class="card-base p-6 md:p-8">
|
<div class="card-base p-6 md:p-8">
|
||||||
|
|
||||||
<div class="flex items-center gap-2 mb-6">
|
<div class="flex items-center gap-2 mb-6">
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ const friends = [
|
|||||||
];
|
];
|
||||||
---
|
---
|
||||||
|
|
||||||
<MainGridLayout title="友链">
|
<MainGridLayout title="友链" description="友情链接页面,收录了一些朋友的博客与网站。">
|
||||||
<!-- 头部 -->
|
<!-- 头部 -->
|
||||||
<div class="flex items-center gap-4 mb-8 mt-4 px-2">
|
<div class="flex items-center gap-4 mb-8 mt-4 px-2">
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ const { Content, headings } = await entry.render();
|
|||||||
|
|
||||||
const { remarkPluginFrontmatter } = await entry.render();
|
const { remarkPluginFrontmatter } = await entry.render();
|
||||||
|
|
||||||
|
const postUrl = new URL(getPostUrlBySlug(entry.slug), Astro.site).href;
|
||||||
|
const coverImage = /^https?:\/\//.test(entry.data.image)
|
||||||
|
? entry.data.image
|
||||||
|
: undefined;
|
||||||
|
|
||||||
const jsonLd = {
|
const jsonLd = {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
"@type": "BlogPosting",
|
"@type": "BlogPosting",
|
||||||
@@ -40,10 +45,17 @@ const jsonLd = {
|
|||||||
url: Astro.site,
|
url: Astro.site,
|
||||||
},
|
},
|
||||||
datePublished: formatDateToYYYYMMDD(entry.data.published),
|
datePublished: formatDateToYYYYMMDD(entry.data.published),
|
||||||
|
...(entry.data.updated && {
|
||||||
|
dateModified: formatDateToYYYYMMDD(entry.data.updated),
|
||||||
|
}),
|
||||||
|
...(coverImage && { image: coverImage }),
|
||||||
|
mainEntityOfPage: {
|
||||||
|
"@type": "WebPage",
|
||||||
|
"@id": postUrl,
|
||||||
|
},
|
||||||
inLanguage: entry.data.lang
|
inLanguage: entry.data.lang
|
||||||
? entry.data.lang.replace("_", "-")
|
? entry.data.lang.replace("_", "-")
|
||||||
: siteConfig.lang.replace("_", "-"),
|
: siteConfig.lang.replace("_", "-"),
|
||||||
// TODO include cover image here
|
|
||||||
};
|
};
|
||||||
---
|
---
|
||||||
<MainGridLayout banner={entry.data.image} title={entry.data.title} description={entry.data.description} lang={entry.data.lang} setOGTypeArticle={true} headings={headings} series={entry.data.series}>
|
<MainGridLayout banner={entry.data.image} title={entry.data.title} description={entry.data.description} lang={entry.data.lang} setOGTypeArticle={true} headings={headings} series={entry.data.series}>
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ import { i18n } from '@i18n/translation'
|
|||||||
import I18nKey from '@i18n/i18nKey'
|
import I18nKey from '@i18n/i18nKey'
|
||||||
---
|
---
|
||||||
|
|
||||||
<MainGridLayout title={i18n(I18nKey.series)}>
|
<MainGridLayout title={i18n(I18nKey.series)} description="按系列浏览博客文章,查看同一主题下的连载内容。">
|
||||||
<SeriesPanel></SeriesPanel>
|
<SeriesPanel></SeriesPanel>
|
||||||
</MainGridLayout>
|
</MainGridLayout>
|
||||||
Reference in new issue
Block a user