资讯动态

使用 next-seo 自定义 JSON-LD 组件:JsonLdScript、Processors 与 @type Optional 模式完整指南

发布时间:2026/10/14 20:24:03 来源:尧图企业网站定制
前端【免费下载链接】next-seoNext SEO is a plug in that makes managing your SEO easier in Next.js projects.项目地址https://gitcode.com/gh_mirrors/ne/next-seo点击查看免费下载本文是 next-seo 仓库中 CUSTOM_COMPONENTS.md 的技术深化解读。next-seo 内置了 Article、Product、Recipe、Event 等 30 个开箱即用的 JSON-LD 组件但当业务需要PodcastEpisode、RealEstateListing、Service、CourseInstance等 Schema.org 类型时内置组件往往无法覆盖。此时可以利用 next-seo 暴露的核心工具JsonLdScript与 60 个processors处理器以与内置组件完全一致的模式打造自定义结构化数据组件。阅读本文后你将掌握如何使用JsonLdScript渲染结构化数据脚本、如何运用内置处理器处理字符串/对象多形态输入、如何编写带类型探测能力的自定义处理器以及如何通过单元测试与 Google Rich Results Test 验证自定义组件的正确性。目录快速开始几行代码创建一个自定义组件核心概念JsonLdScript、type Optional 模式与 Processors使用内置 Processors四大类常见 Schema.org 类型创建自定义 Processors高级模式嵌套处理、条件属性与多类型支持最佳实践真实项目示例仓库中的四个自定义组件测试你的自定义组件从内联 JSON-LD 迁移到 next-seo 自定义组件Processor API 参考快速开始几行代码创建一个自定义组件next-seo 在设计上把渲染与数据处理拆成了两个独立可复用的核心能力这正是自定义组件的基石。一个最小可用的自定义组件只需要两步构造结构化数据对象然后交给JsonLdScript渲染。import { JsonLdScript, processors } from next-seo; export function PodcastEpisodeJsonLd({ name, author, duration, url }) { const data { context: https://schema.org, type: PodcastEpisode, name, ...(url { url }), ...(duration { duration }), ...(author { author: processors.processAuthor(author) }), }; return JsonLdScript data{data} scriptKeypodcast-episode /; } // 使用方式 —— 使用者无需手写 type PodcastEpisodeJsonLd nameEpisode 1: Getting Started authorJane Doe // 简单字符串即可 durationPT30M urlhttps://example.com/episode-1 /;注意两个关键点JsonLdScript与processors均由 next-seo 的公共入口 src/index.ts 统一导出其中processors以命名空间形式导出全部处理函数JsonLdScript为独立组件导出自定义组件内部用...(condition { key: value })的条件展开语法控制可选属性只有传入值时才输出对应字段这与内置组件如 ArticleJsonLd的写法完全一致。核心概念JsonLdScript、type Optional 模式与 Processors1. JsonLdScript 组件渲染层JsonLdScript是所有结构化数据无论内置还是自定义的最终渲染出口。其源码位于 src/core/JsonLdScript.tsx接收四个 propsimport { JsonLdScript } from next-seo; JsonLdScript data{yourStructuredData} id{optionalId} // 可选HTML id 属性 scriptKey{requiredKey} // 必填React key用于 script 元素 nonce{optionalNonce} // 可选CSP nonce 值 /;从实现看它内部做了三件重要的事空数据保护当data为null或undefined时直接返回null不会渲染出空脚本标签JsonLdScript.tsx安全序列化调用stringify工具将数据转为 JSON 字符串。stringify见 src/utils/stringify.ts除了用safeJsonLdReplacer剔除null值还会对/script大小写不敏感、!--、--等危险序列做 Unicode 转义防止结构化数据注入 HTML 时被解析器误判为脚本结束或注释——这是 W3C JSON-LD 规范对 script 元素内容的安全要求默认 id 回退未传id时脚本的 HTMLid会回退为scriptKey同时data-testid也会被设为id便于测试定位JsonLdScript.tsx。其单元测试位于 src/core/JsonLdScript.test.tsx覆盖了标签类型、序列化内容、id 回退以及空数据返回null等场景。2. type Optional 模式核心设计原则next-seo 的核心理念是开发者永远不需要手动指定type。这是通过智能处理器自动补全正确的 Schema.org 类型实现的。// 使用方这样写 authorJohn Doe // 处理器自动转换为 { type: Person, name: John Doe }这一原则也体现在内置组件的类型定义上ArticleJsonLdProps使用OmitArticle, type这类工具类型让使用者在 props 层面根本看不到type字段详见 ADDING_NEW_COMPONENTS.md 中关于type的设计说明。自定义组件应当继承并复刻这套模式。3. Processors灵活输入 → 规范化 Schema.org 对象处理器Processors是一类把灵活输入转换成带正确type的 Schema.org 对象的纯函数import { processors } from next-seo; const author processors.processAuthor(John Doe); const image processors.processImage({ url: image.jpg, width: 800 }); const address processors.processAddress(123 Main St);几乎所有处理器都遵循相同的底层实现范式即位于 src/utils/processors.ts 的通用工具函数processSchemaTypeexport function processSchemaTypeT extends { type: string }( value: unknown, schemaType: string, stringHandler?: (str: string) OmitT, type, numberHandler?: (num: number) OmitT, type, ): T { // 字符串输入 提供 stringHandler → 按 handler 构造对象 // 数字输入 提供 numberHandler → 按 handler 构造对象 // 已带 type 的对象 → 原样返回forward compatible // 普通对象 → 自动补上 type 并展开 // 兜底 → 仅返回 { type: schemaType } }该函数在 src/utils/processors.ts 中实现它决定了灵活输入的四条处理规则字符串可以配合stringHandler被转换为带type的对象例如processAddress(123 Main St)会生成{ type: PostalAddress, streetAddress: 123 Main St }数字可以配合numberHandler转换例如processNumberOfEmployees(500)会生成{ type: QuantitativeValue, value: 500 }已含type的对象会被原样保留保证向前兼容无type的普通对象会自动补上type后展开从而让使用者始终无需关心类型字段。使用内置 Processors四大类常见 Schema.org 类型processors命名空间通过 src/utils/processors.export.ts 统一导出按领域分为 People Organizations、Media Content、Locations Places、Commerce Offers、Reviews Ratings、HowTo Content、Membership Loyalty、Specifications Values、Products、Education Requirements、Data Creative Works、Accommodation Rental、Interaction Statistics 等十余个类别总数超过 60 个。下面按文档给出的四大类逐一展开。人物与组织People Organizationsimport { processors } from next-seo; // 灵活的 author 输入 processors.processAuthor(Jane Doe); // → { type: Person, name: Jane Doe } processors.processAuthor({ name: ACME Corp, logo: ... }); // → { type: Organization, ... } // 其他人物/组织处理器 processors.processPublisher(Tech Publishing); processors.processOrganizer({ name: Event Co, url: ... }); processors.processPerformer(Band Name);processAuthor的实现src/utils/processors.ts展示了智能类型探测的典型逻辑字符串输入通过内置的组织特征词表magazine、publication、company、corp、inc、llc、ltd、group、foundation、institute、association、press、media、studio 等 20 个关键词判断字符串更像组织还是个人命中则输出Organization否则输出Person已带type的对象原样返回普通对象依据是否含有logo、address、contactPoint等组织专属属性判定为Organization并递归处理其嵌套的 logo、address、contactPoint 字段processOrganizationFields辅助函数否则默认为Person。processPerformer与processOrganizer则通过检测familyName、givenName、additionalName这类 Person 专属属性来区分个人与团体/组织src/utils/processors.ts。媒体与内容Media Content// 图片 —— 字符串 URL 或 ImageObject 对象 processors.processImage(https://example.com/image.jpg); processors.processImage({ url: ..., width: 800, height: 600 }); // 视频 processors.processVideo({ name: Tutorial, uploadDate: 2024-01-01, thumbnailUrl: ..., }); // 其他媒体处理器 processors.processLogo(logo.jpg); processors.processScreenshot({ url: ..., caption: App screenshot });注意processImage的一个特殊约定当输入是字符串 URL 时它原样返回字符串不包裹type只有对象输入才补上ImageObject类型src/utils/processors.ts。这是因为 Schema.org 允许image字段直接使用 URL 字符串这样的输出更简洁。processLogo直接委托给processImagesrc/utils/processors.ts。该类别下还包括processVideo、processClip、processBroadcastEvent、processSeekToAction、processThreeDModel等。位置与场所Locations Places// 简单字符串 → PostalAddress processors.processAddress(123 Main St, City, Country); // 携带更多细节的对象 processors.processAddress({ streetAddress: 123 Main St, addressLocality: San Francisco, addressRegion: CA, postalCode: 94105, addressCountry: US, }); // 带地理坐标的场所 processors.processPlace({ name: Office, geo: { latitude: 37.7749, longitude: -122.4194 }, });processAddress借助processSchemaType的stringHandler参数把字符串包装为{ type: PostalAddress, streetAddress: str }src/utils/processors.tsprocessPlace的字符串处理则更复杂一些字符串会同时生成name字段和一个内嵌的PostalAddresssrc/utils/processors.ts。此外还有processGeo、processJobLocation、processSpatialCoverage、processDefinedRegion等位置相关处理器。商业与优惠Commerce Offers// 产品优惠 processors.processProductOffer({ price: 29.99, priceCurrency: USD, availability: https://schema.org/InStock, }); // 退货政策 processors.processMerchantReturnPolicy({ returnPolicyCategory: https://schema.org/MerchantReturnFiniteReturnWindow, merchantReturnDays: 30, });这类处理器不仅会补全type还会做归一化与嵌套处理。以processMerchantReturnPolicy为例src/utils/processors.ts它会把applicableCountry、returnPolicyCountry、returnMethod、refundType、itemCondition等字段统一归一化为数组形式用processSimpleMonetaryAmount递归处理returnShippingFeesAmount、restockingFee等金额字段支持数字输入数字会默认带上currency: USD见 src/utils/processors.ts对returnPolicySeasonalOverride做批量处理。该类别还包括processOffer、processAggregateOffer、processPriceSpecification、processUnitPriceSpecification、processMonetaryAmount、processOfferShippingDetails、processShippingDeliveryTime等。创建自定义 Processors当内置处理器无法覆盖业务形态时你可以自己编写处理器并自由组合内置工具。基础自定义处理器import { processors } from next-seo; // 为播客主持人定制的处理器 function processHost(host: string | { name: string; bio?: string }) { if (typeof host string) { return { type: Person, name: host, }; } // 对象输入复用通用 helper return processors.processSchemaType(host, Person); } // 在组件中使用 export function PodcastJsonLd({ hosts, ...props }) { const data { context: https://schema.org, type: PodcastSeries, ...(hosts { host: Array.isArray(hosts) ? hosts.map(processHost) : processHost(hosts), }), }; return JsonLdScript data{data} scriptKeypodcast /; }这里展示了两个要点字符串输入直接手工构造对象对象输入则委托给processSchemaType(host, Person)—— 该通用函数会自动为无type的对象补上type: Person若对象已带type则原样透传。带类型探测的高级自定义处理器当同一个输入可能对应多个 Schema.org 子类型时可以根据属性做启发式探测function processCreativeWork(work: string | Recordstring, any) { if (typeof work string) { return { type: CreativeWork, name: work, }; } // 已带 type原样返回 if (work[type]) { return work; } // 根据属性探测类型 let type CreativeWork; if (isbn in work) type Book; else if (director in work) type Movie; else if (artist in work) type MusicRecording; return { type: type, ...work, }; }这种按属性探测 → 补全类型的模式与内置processAuthor、processPerformer的实现思路一脉相承先检查type再检查领域专属属性logo/address/contactPoint→ OrganizationfamilyName/givenName→ Person最后落入默认类型兜底。高级模式嵌套处理、条件属性与多类型支持嵌套处理Nested Processing结构化数据经常是层层嵌套的处理器可以在嵌套过程中逐层递归转换function processEventWithVenue(event: { name: string; venue?: string | { name: string; address?: string }; organizer?: string | { name: string }; }) { return { type: Event, name: event.name, ...(event.venue { location: typeof event.venue string ? processors.processPlace(event.venue) : processors.processPlace({ ...event.venue, ...(event.venue.address { address: processors.processAddress(event.venue.address), }), }), }), ...(event.organizer { organizer: processors.processOrganizer(event.organizer), }), }; }注意嵌套层次venue字符串走processPlace生成Place 内嵌PostalAddressvenue对象则先把内部的address用processAddress处理成PostalAddress再整体交给processPlace。内置处理器的嵌套逻辑同样如此——processReview会递归处理内部的reviewRating→Rating与author→Person/Organization见 src/utils/processors.ts。条件属性Conditional Properties只渲染有值的属性避免输出空字段是 next-seo 全仓库统一的书写风格export function CustomProductJsonLd({ name, description, price, image, brand, reviews, aggregateRating, ...props }) { const data { context: https://schema.org, type: Product, name, ...(description { description }), ...(price { offers: { type: Offer, price, priceCurrency: USD, }, }), ...(image { image: Array.isArray(image) ? image.map(processors.processImage) : processors.processImage(image), }), ...(brand { brand: processors.processBrand(brand) }), ...(reviews { review: Array.isArray(reviews) ? reviews.map(processors.processReview) : processors.processReview(reviews), }), ...(aggregateRating { aggregateRating: processors.processAggregateRating(aggregateRating), }), }; return JsonLdScript data{data} scriptKey{props.scriptKey || product} /; }两个值得注意的细节数组与单值的统一处理image、reviews都通过Array.isArray分支单值走单个处理器数组走.map()批量处理——这与 ArticleJsonLd 对author、image的处理方式一致scriptKey 回退scriptKey{props.scriptKey || product}允许调用方覆盖默认 key避免同页面多个组件实例发生 React key 冲突。多类型支持Multiple Schema Types当同一个组件的输出可能属于多个相近的 Schema.org 类型时用typeprop 让调用方选择type ScholarlyArticleType | ScholarlyArticle | MedicalScholarlyArticle | TechArticle; export function ScholarlyArticleJsonLd({ type ScholarlyArticle, headline, author, datePublished, journal, doi, ...props }: { type?: ScholarlyArticleType; headline: string; author: string | Arraystring | { name: string }; datePublished: string; journal?: string; doi?: string; }) { const data { context: https://schema.org, type: type, headline, datePublished, ...(author { author: Array.isArray(author) ? author.map(processors.processAuthor) : processors.processAuthor(author), }), ...(journal { isPartOf: { type: PublicationIssue, name: journal, }, }), ...(doi { identifier: processors.processIdentifier(doi) }), }; return ( JsonLdScript data{data} scriptKey{props.scriptKey || article-${type}} / ); }这里type直接取自typeprop带默认值author依然交给processAuthor做灵活输入处理doi通过processIdentifier规范化。注意scriptKey依据 type 动态生成article-${type}确保同一页面渲染多个类型的实例时 key 不冲突。最佳实践1. 灵活类型一律走处理器// ✅ 推荐 —— 使用处理器 author: processors.processAuthor(author) // ❌ 不推荐 —— 强迫使用者手写 type author: { type: Person, ...author }使用处理器意味着调用方传字符串可以、传无type对象可以、传已带type对象也可以全部分支都被正确收敛。2. 同时支持数组与单值// 支持单值与数组两种输入 ...(tags { keywords: Array.isArray(tags) ? tags.join(, ) : tags })3. 应用合理的默认值// dateModified 缺省时回退到 datePublished const data { datePublished, dateModified: dateModified || datePublished, };这一惯例同样存在于内置组件中ArticleJsonLd在未传dateModified但传了datePublished时会自动以datePublished填充dateModified参见 ADDING_NEW_COMPONENTS.md 的默认值示例。4. 用 TypeScript 提升开发体验interface ServiceJsonLdProps { name: string; provider?: string | Organization; areaServed?: string | string[]; serviceType?: string; scriptId?: string; scriptKey?: string; }5. 为组件编写文档注释/** * ServiceJsonLd - Structured data for service offerings * * example * ServiceJsonLd * nameWeb Development * providerTech Agency * areaServed{[US, CA, UK]} * serviceTypeProfessional Service * / */ export function ServiceJsonLd({ ... }) { ... }真实项目示例仓库中的四个自定义组件为了证明这套方法论的可落地性仓库在 examples/app-router-showcase/components/custom 下提供了 4 个完整的自定义组件实现并配套了真实示例页面与端到端测试。1. PodcastSeriesJsonLd播客系列 单集列表实现位于 examples/app-router-showcase/components/custom/PodcastSeriesJsonLd.tsx演示页面为 examples/app-router-showcase/app/custom-podcast/page.tsx。组件host属性接受字符串或对象统一经processors.processAuthor转换字符串 Sarah Johnson →{ type: Person, name: Sarah Johnson }image经processors.processImage处理字符串 URL 原样保留episodes数组逐项映射为PodcastEpisode子类型并自动按index 1生成position字段如Episode 1→position: 1文档中的同主题代码片段在此基础上还展示了description、datePublished、url等可选字段的处理。2. ServiceJsonLd服务 提供方 评分实现位于 examples/app-router-showcase/components/custom/ServiceJsonLd.tsx演示页面为 examples/app-router-showcase/app/custom-service/page.tsx。该组件展示了三种能力的组合provider接受字符串或含logo、address等字段的复杂对象统一经processors.processOrganization转换嵌套的address会被自动升级为PostalAddressareaServed统一归一化为数组Array.isArray(areaServed) ? areaServed : [areaServed]aggregateRating经processors.processAggregateRating补全AggregateRating类型。3. 文档中的四个进阶示例CUSTOM_COMPONENTS.md 还给出了四个可直接借鉴的完整组件模板Podcast Series with Episodes完整的PodcastSeriesProps接口 单集position自动编号Real Estate ListingpriceCurrency带USD默认值images数组批量processImagefloorSize走processQuantitativeValueService with Pricing TierspricingTiers映射为hasOfferCatalog→OfferCatalog→Offer层级价格支持number或{ min, max }区间两种形态区间走PriceSpecificationminPrice/maxPriceEducational Course with Modulesmodules映射为hasCourseInstance→CourseInstance含courseMode: online与positionprice存在时生成带validFrom/validThrough的Offer。4. 仓库内的可验证事实这些组件的实际渲染行为被 tests/e2e/customComponents.e2e.spec.ts 完整验证例如字符串host被转换为Person且带type第 25-28 行断言字符串图片 URL 保持字符串形态不包装对象第 30-31 行断言provider复杂对象被补上Organization其嵌套address被补上PostalAddress第 70-83 行断言全部嵌套对象PodcastSeries、Person、PodcastEpisode、Service、Organization、PostalAddress、Offer、AggregateRating都无需调用方手写type第 138-168 行断言输出 JSON 可被JSON.parse正常解析且包含context、type、name三个基础字段第 170-194 行断言。此外tests/e2e/jsonValidation.e2e.spec.ts 也针对这两个自定义组件补充了 JSON 合法性校验。测试你的自定义组件单元测试自定义组件与内置组件共用完全相同的测试手段——渲染后从 DOM 中取出script[typeapplication/ldjson]解析其文本内容并断言import { render } from testing-library/react; import { ServiceJsonLd } from ./ServiceJsonLd; describe(ServiceJsonLd, () { it(renders service with basic props, () { const { container } render( ServiceJsonLd nameConsulting Service providerTech Solutions Inc /, ); const script container.querySelector( script[typeapplication/ldjson], ); const data JSON.parse(script.textContent); expect(data[type]).toBe(Service); expect(data.name).toBe(Consulting Service); expect(data.provider[type]).toBe(Organization); }); });这种测试模式与 src/core/JsonLdScript.test.tsx 以及src/components/下各内置组件的.test.tsx完全同构自定义组件可以直接复用。端到端验证仓库的 E2E 规范要求测试必须走真实示例页面而非 mock 注入详见 ADDING_NEW_COMPONENTS.md 的 E2E 指南先在examples/app-router-showcase/app/下创建页面再由 Playwright 脚本page.goto()导航并断言真实渲染输出。搜索引擎验证部署后使用 Google 的 Rich Results Testsearch.google.com/test/rich-results输入页面 URL 校验结构化数据确认无错误、无警告。这是结构化数据上线前的最后一道质量门。从内联 JSON-LD 迁移到 next-seo 自定义组件如果你此前在页面里手写script typeapplication/ldjson迁移到自定义组件后代码会更干净、类型更安全。迁移前内联 JSON-LDscript typeapplication/ldjson dangerouslySetInnerHTML{{ __html: JSON.stringify({ context: https://schema.org, type: Service, name: My Service, provider: { type: Organization, name: My Company, }, }), }} /迁移后自定义组件import { JsonLdScript, processors } from next-seo; export function ServiceJsonLd({ name, provider }) { const data { context: https://schema.org, type: Service, name, provider: processors.processOrganization(provider), }; return JsonLdScript data{data} scriptKeyservice /; } // 使用方式 —— 更简洁且类型安全 ServiceJsonLd nameMy Service providerMy Company // 无需 type /;迁移收益包括不再手动JSON.stringifystringify已内置安全转义、不再写dangerouslySetInnerHTML、provider可以是任意灵活输入、整套输出可被 TypeScript 类型约束。Processor API 参考完整的处理器清单见 src/utils/processors.export.ts。核心处理器速查处理器输入 → 输出processSchemaType(value, type)任意输入 → 指定 Schema.org 类型的通用处理器processAuthor(author)字符串/对象 →Person或Organization带类型探测processImage(image)字符串 URL 或ImageObject→ 字符串原样保留或ImageObjectprocessAddress(address)字符串或PostalAddress→PostalAddressprocessPlace(place)字符串或Place→Place含内嵌地址processOffer(offer)对象 →Offer含价格与 availabilityprocessReview(review)对象 →Review递归处理 rating 与 authorprocessAggregateRating(rating)对象 →AggregateRating含评分与计数其余 50 个覆盖 HowTo、视频、课程、求职、数据目录、会员体系、住宿等领域的专用处理器总结基于 next-seo 创建自定义 JSON-LD 组件的完整流程从next-seo导入JsonLdScript与processors定义组件 props推荐使用 TypeScript 联合类型描述灵活输入对灵活类型一律使用处理器转换遵循 type Optional 模式让调用方永远不用手写type返回JsonLdScript data{data} scriptKey{...} /。这套方法可以带来TypeScript 全程类型安全、字符串/对象/数组多形态输入的统一收敛、调用方零type样板代码、与内置组件完全一致的代码风格以及易于单元测试和 E2E 验证的组件结构。延伸阅读内置组件的实现模式查看 src/components 下的 30 个*JsonLd.tsx组件及其同名.test.tsx在 next-seo 中新增一个内置组件的完整流程阅读 ADDING_NEW_COMPONENTS.md处理器底层实现与类型探测逻辑src/utils/processors.ts安全序列化与转义实现src/utils/stringify.ts自定义组件的真实运行样例examples/app-router-showcase/components/custom 及对应 E2E 测试 tests/e2e/customComponents.e2e.spec.ts赞分享前端【免费下载链接】next-seoNext SEO is a plug in that makes managing your SEO easier in Next.js projects.项目地址https://gitcode.com/gh_mirrors/ne/next-seo点击查看免费下载相关推荐Next SEO 开发者指南JSON-LD 组件架构与「type 可选」设计模式实战解析Next SEO 开发者指南JSON LD 组件架构与「type 可选」设计模式实战解析 导读 next seo 是一个面向 Next.js 项目的 SEO前端next-seo 仓库开发指南命令工作流、项目架构与 type Optional Pattern 设计模式解析next seo 仓库开发指南命令工作流、项目架构与 type Optional Pattern 设计模式解析 导读 本文基于 next seo 仓库根目录前端Next.js 中集成 next-seo默认 SEO、Open Graph 与 JSON-LD 结构化数据的完整示例解析Next.js 中集成 next seo默认 SEO、Open Graph 与 JSON LD 结构化数据的完整示例解析 在 Next.js 应用中统一管理前端后端Web框架SSR前端构建上一篇immutable-js基准测试Benchmark.js性能对比下一篇Hacker-job 完整指南利用 AI 智能解析 Hacker News 招聘帖子的 10 个技巧创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑