SEO 优化
站点元信息、Open Graph、JSON-LD 结构化数据与 sitemap 配置
概览
@repo/seo 提供一组 TanStack Start SEO 工具,统一管理页面标题、描述、Open Graph、Twitter Card 和 JSON-LD 结构化数据。
- 生成 TanStack Start route
head()对象 - 配置站点级名称、域名、作者和默认语言
- 输出 Open Graph 和 Twitter Card
- 安全输出 JSON-LD script tag
环境变量
| 变量名 | 说明 | 示例 |
|---|---|---|
VITE_WEB_URL | 站点完整 URL(含协议,不带尾部斜杠) | https://yourdomain.com |
站点名称、默认描述、作者、关键词和 Open Graph 语言映射维护在 products/01mvp/apps/web/src/config/app.config.ts。SEO helper 读取这份配置,不再单独新增 VITE_SITE_NAME 这类环境变量。
如果你要改成自己的产品文案,可以把默认描述换成类似「面向独立开发者的 AI 产品实战模板」这种一句话定位。
站点元信息
定义站点配置
应用层配置在 products/01mvp/apps/web/src/shared/lib/seo.ts,它把通用包 @repo/seo 和当前站点配置接起来:
import type { TanStackStartSeoSite } from "@repo/seo";
import { appConfig } from "@/config/app.config";
export const seoSite = {
applicationName: appConfig.site.longName,
baseUrl: appConfig.site.baseUrl,
defaultDescription: appConfig.site.description,
defaultImages: [
{
alt: appConfig.site.longName,
height: 630,
type: "image/png",
url: `${appConfig.site.baseUrl}/og/index.png`,
width: 1200,
},
],
defaultTitle: appConfig.site.defaultTitle,
siteName: appConfig.site.longName,
titleTemplate: `%s | ${appConfig.site.shortName}`,
} satisfies TanStackStartSeoSite;封装应用级 SEO helper
项目里再封装一层,把站点配置和多语言默认值放进去。页面只需要传当前页面的 title、description、canonical 和 locale:
import {
type GenerateTanStackStartSeoParams,
generateTanStackStartSeo,
type TanStackStartSeoAlternates,
} from "@repo/seo";
import { appConfig } from "@/config/app.config";
import { seoSite } from "./seo-site";
type AppSeoOptions = Omit<GenerateTanStackStartSeoParams, "alternates" | "site"> & {
alternates?: Omit<TanStackStartSeoAlternates, "baseLocale" | "locales">;
};
export function generateAppSeo({ alternates, ...options }: AppSeoOptions) {
return generateTanStackStartSeo({
...options,
alternates: alternates
? {
...alternates,
baseLocale: appConfig.i18n.baseLocale,
locales: appConfig.i18n.locales,
}
: undefined,
site: seoSite,
});
}为页面生成 head
在 TanStack Start route 中使用 head():
import { createFileRoute } from "@tanstack/react-router";
import { generateAppSeo } from "@/shared/lib/seo";
export const Route = createFileRoute("/{-$locale}/(root-layout)/pricing/")({
head: ({ params }) =>
generateAppSeo({
alternates: {
canonicalPath: "/pricing",
locale: params.locale,
},
description: "选择适合你的 01MVP 会员计划。",
title: "定价",
}),
});generateTanStackStartSeo() 会自动拼接站点名称,并生成 Open Graph 和 Twitter Card 的默认值。
如果一个页面没有传 title,会使用站点默认标题。默认标题应该本身就能读懂,比如 AI 产品实战手册,避免模板拼接后出现 01MVP | 01MVP。
动态页面
export const Route = createFileRoute("/{-$locale}/(root-layout)/digital-products/$slug/")({
loader: async ({ params }) => getProduct(params.slug),
head: ({ loaderData, params }) =>
generateAppSeo({
alternates: {
canonicalPath: `/digital-products/${loaderData.slug}`,
locale: params.locale,
},
description: loaderData.summary,
images: loaderData.coverImage
? [{ alt: loaderData.title, url: loaderData.coverImage }]
: undefined,
openGraphType: "website",
title: loaderData.title,
}),
});Open Graph 和 Twitter Card
generateTanStackStartSeo() 会输出 og:* 和 twitter:* meta。你可以按页面覆盖图片、类型和 robots:
head: () =>
generateAppSeo({
alternates: {
canonicalPath: "/pricing",
},
description: "选择适合你的计划",
images: [
{
alt: "产品定价",
height: 630,
type: "image/png",
url: "/images/pricing-og.png",
width: 1200,
},
],
openGraphType: "website",
title: "产品定价",
}),页面新增检查
新增页面时,按页面性质处理 SEO:
| 页面类型 | 要做的事 |
|---|---|
| 公开营销页、内容页、产品页 | 写 route head(),传 title、description、canonicalPath 和 locale |
| 需要被搜索引擎发现的公开页 | 同步更新 products/01mvp/apps/web/src/lib/sitemap.ts 的公开路由列表 |
| Dashboard、Admin、登录、设置、搜索页 | 加 robots: { index: false, follow: false } |
| 结账、订单、领取、支付结果页 | 加 robots: { index: false, follow: false },避免索引事务页面 |
| 活动页、落地页、旧路径别名 | 优先 redirect;保留页面时,把 canonical 指向主页面,并按需 noindex |
JSON-LD 结构化数据
JSON-LD 帮助搜索引擎理解页面内容。TanStack Start 的 head() 支持 script tag,可以直接传给 scripts:
import { createJsonLdScript } from "@repo/seo";
head: () =>
generateAppSeo({
scripts: [
createJsonLdScript({
"@context": "https://schema.org",
"@type": "WebSite",
name: "01MVP",
url: "https://01mvp.com",
}),
],
});createJsonLdScript() 会统一做 JSON 序列化和 HTML script 安全转义。
根路由可以放稳定的站点级结构化数据:
head: () => {
const rootSeo = generateAppSeo({
includeDocumentMeta: true,
scripts: [
createJsonLdScript({
"@context": "https://schema.org",
"@type": "WebSite",
description: appConfig.site.description,
inLanguage: appConfig.i18n.locales,
name: appConfig.site.longName,
publisher: {
"@type": "Organization",
name: appConfig.site.publisher,
url: appConfig.site.baseUrl,
},
url: appConfig.site.url,
}),
],
});
return {
links: rootSeo.links,
meta: rootSeo.meta,
scripts: rootSeo.scripts,
};
};常用 JSON-LD 类型
const organizationSchema = {
"@context": "https://schema.org",
"@type": "Organization",
name: "01MVP",
url: "https://01mvp.com",
logo: "https://01mvp.com/images/logo.png",
sameAs: ["https://x.com/makerjackie", "https://github.com/makerjackie"],
};
const articleSchema = {
"@context": "https://schema.org",
"@type": "Article",
headline: "如何快速启动一个 SaaS 项目",
author: { "@type": "Person", name: "Your Name" },
datePublished: "2025-01-15",
image: "https://01mvp.com/images/post-cover.png",
};
const productSchema = {
"@context": "https://schema.org",
"@type": "Product",
name: "01MVP Pro",
description: "适用于独立开发者的全栈模板",
offers: {
"@type": "Offer",
price: "99",
priceCurrency: "USD",
},
};Sitemap 集成
TanStack Start 可以直接用 TanStack Router 的 server route 输出 sitemap。本模板在 products/01mvp/apps/web/src/routes/sitemap[.]xml.ts 中实现:
import { createFileRoute } from "@tanstack/react-router";
import { buildSitemapResponse } from "@/server/seo";
export const Route = createFileRoute("/sitemap.xml")({
preload: false,
server: {
handlers: {
GET: () => buildSitemapResponse(),
},
},
});products/01mvp/apps/web/src/server/seo.ts 负责读取站点配置、加载文档源并返回 Response。createSitemapEntries() 负责合并公开页面和文档页,serializeSitemap() 负责输出 XML。新增公开索引页面时,更新 products/01mvp/apps/web/src/lib/sitemap.ts 中的公开路由列表。
robots.txt
同样用 server route 输出 robots.txt,文件位置是 products/01mvp/apps/web/src/routes/robots[.]txt.ts:
import { createFileRoute } from "@tanstack/react-router";
import { buildRobotsTxtResponse } from "@/server/seo";
export const Route = createFileRoute("/robots.txt")({
server: {
handlers: {
GET: () => buildRobotsTxtResponse(),
},
},
});注意事项
这个包是可选能力。如果项目只需要基本的 SEO,TanStack Start route head() 已经够用。当多个页面或多个 app 需要共享 SEO 默认值时,再接入 @repo/seo。
相关资源
这篇文档有问题?