☰
使用 next-seo 自定义 JSON-LD 组件:JsonLdScript、Processors 与 @type Optional 模式完整指南
2026/10/14 20:23:46 网站建设 项目流程
  • 前端

【免费下载链接】next-seo

Next 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 验证自定义组件的正确性。

目录

  1. 快速开始:几行代码创建一个自定义组件
  2. 核心概念:JsonLdScript、@type Optional 模式与 Processors
  3. 使用内置 Processors:四大类常见 Schema.org 类型
  4. 创建自定义 Processors
  5. 高级模式:嵌套处理、条件属性与多类型支持
  6. 最佳实践
  7. 真实项目示例:仓库中的四个自定义组件
  8. 测试你的自定义组件
  9. 从内联 JSON-LD 迁移到 next-seo 自定义组件
  10. 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} scriptKey="podcast-episode" />; } // 使用方式 —— 使用者无需手写 @type! <PodcastEpisodeJsonLd name="Episode 1: Getting Started" author="Jane Doe" // 简单字符串即可! duration="PT30M" url="https://example.com/episode-1" />;

注意两个关键点:

  • JsonLdScript与processors均由 next-seo 的公共入口 src/index.ts 统一导出,其中processors以命名空间形式导出全部处理函数,JsonLdScript为独立组件导出;
  • 自定义组件内部用...(condition && { key: value })的条件展开语法控制可选属性,只有传入值时才输出对应字段,这与内置组件(如 ArticleJsonLd)的写法完全一致。

核心概念:JsonLdScript、@type Optional 模式与 Processors

1. JsonLdScript 组件:渲染层

JsonLdScript是所有结构化数据(无论内置还是自定义)的最终渲染出口。其源码位于 src/core/JsonLdScript.tsx,接收四个 props:

import { JsonLdScript } from "next-seo"; <JsonLdScript data={yourStructuredData} id={optionalId} // 可选:HTML id 属性 scriptKey={requiredKey} // 必填:React key,用于 script 元素 nonce={optionalNonce} // 可选:CSP nonce 值 />;

从实现看,它内部做了三件重要的事:

  1. 空数据保护:当data为null或undefined时直接返回null,不会渲染出空脚本标签(JsonLdScript.tsx);
  2. 安全序列化:调用stringify工具将数据转为 JSON 字符串。stringify(见 src/utils/stringify.ts)除了用safeJsonLdReplacer剔除null值,还会对</script>(大小写不敏感)、<!--、-->等危险序列做 Unicode 转义,防止结构化数据注入 HTML 时被解析器误判为脚本结束或注释——这是 W3C JSON-LD 规范对 script 元素内容的安全要求;
  3. 默认 id 回退:未传id时,脚本的 HTMLid会回退为scriptKey,同时data-testid也会被设为id,便于测试定位(JsonLdScript.tsx)。

其单元测试位于 src/core/JsonLdScript.test.tsx,覆盖了标签类型、序列化内容、id 回退以及空数据返回null等场景。

2. @type Optional 模式:核心设计原则

next-seo 的核心理念是:开发者永远不需要手动指定@type。这是通过"智能处理器"自动补全正确的 Schema.org 类型实现的。

// 使用方这样写: author="John Doe" // 处理器自动转换为: { "@type": "Person", name: "John Doe" }

这一原则也体现在内置组件的类型定义上:ArticleJsonLdProps使用Omit<Article, "@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 的通用工具函数processSchemaType:

export function processSchemaType<T extends { "@type": string }>( value: unknown, schemaType: string, stringHandler?: (str: string) => Omit<T, "@type">, numberHandler?: (num: number) => Omit<T, "@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 & Organizations)

import { 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直接委托给processImage(src/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.ts);processPlace的字符串处理则更复杂一些:字符串会同时生成name字段和一个内嵌的PostalAddress(src/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} scriptKey="podcast" />; }

这里展示了两个要点:字符串输入直接手工构造对象;对象输入则委托给processSchemaType(host, "Person")—— 该通用函数会自动为无@type的对象补上"@type": "Person",若对象已带@type则原样透传。

带类型探测的高级自定义处理器

当同一个输入可能对应多个 Schema.org 子类型时,可以根据属性做启发式探测:

function processCreativeWork(work: string | Record<string, 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→ Organization,familyName/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+ 内嵌PostalAddress);venue对象则先把内部的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"} />; }

两个值得注意的细节:

  1. 数组与单值的统一处理:image、reviews都通过Array.isArray分支,单值走单个处理器,数组走.map()批量处理——这与 ArticleJsonLd 对author、image的处理方式一致;
  2. 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 | Array<string | { 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 * name="Web Development" * provider="Tech Agency" * areaServed={["US", "CA", "UK"]} * serviceType="Professional 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会被自动升级为PostalAddress;
  • areaServed统一归一化为数组(Array.isArray(areaServed) ? areaServed : [areaServed]);
  • aggregateRating经processors.processAggregateRating补全AggregateRating类型。

3. 文档中的四个进阶示例

CUSTOM_COMPONENTS.md 还给出了四个可直接借鉴的完整组件模板:

  • Podcast Series with Episodes:完整的PodcastSeriesProps接口 + 单集position自动编号;
  • Real Estate Listing:priceCurrency带"USD"默认值,images数组批量processImage,floorSize走processQuantitativeValue;
  • Service with Pricing Tiers:pricingTiers映射为hasOfferCatalog→OfferCatalog→Offer层级,价格支持number或{ min, max }区间两种形态(区间走PriceSpecification+minPrice/maxPrice);
  • Educational Course with Modules:modules映射为hasCourseInstance→CourseInstance(含courseMode: "online"与position),price存在时生成带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[type="application/ld+json"],解析其文本内容并断言:

import { render } from "@testing-library/react"; import { ServiceJsonLd } from "./ServiceJsonLd"; describe("ServiceJsonLd", () => { it("renders service with basic props", () => { const { container } = render( <ServiceJsonLd name="Consulting Service" provider="Tech Solutions Inc" />, ); const script = container.querySelector( 'script[type="application/ld+json"]', ); 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 Test(search.google.com/test/rich-results)输入页面 URL 校验结构化数据,确认无错误、无警告。这是结构化数据上线前的最后一道质量门。


从内联 JSON-LD 迁移到 next-seo 自定义组件

如果你此前在页面里手写<script type="application/ld+json">,迁移到自定义组件后代码会更干净、类型更安全。

迁移前(内联 JSON-LD)

<script type="application/ld+json" 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} scriptKey="service" />; } // 使用方式 —— 更简洁且类型安全! <ServiceJsonLd name="My Service" provider="My Company" // 无需 @type! />;

迁移收益包括:不再手动JSON.stringify(stringify已内置安全转义)、不再写dangerouslySetInnerHTML、provider可以是任意灵活输入、整套输出可被 TypeScript 类型约束。


Processor API 参考

完整的处理器清单见 src/utils/processors.export.ts。核心处理器速查:

处理器输入 → 输出
processSchemaType(value, type)任意输入 → 指定 Schema.org 类型的通用处理器
processAuthor(author)字符串/对象 →Person或Organization(带类型探测)
processImage(image)字符串 URL 或ImageObject→ 字符串原样保留或ImageObject
processAddress(address)字符串或PostalAddress→PostalAddress
processPlace(place)字符串或Place→Place(含内嵌地址)
processOffer(offer)对象 →Offer(含价格与 availability)
processReview(review)对象 →Review(递归处理 rating 与 author)
processAggregateRating(rating)对象 →AggregateRating(含评分与计数)
其余 50+ 个覆盖 HowTo、视频、课程、求职、数据目录、会员体系、住宿等领域的专用处理器

总结

基于 next-seo 创建自定义 JSON-LD 组件的完整流程:

  1. 从next-seo导入JsonLdScript与processors;
  2. 定义组件 props(推荐使用 TypeScript 联合类型描述灵活输入);
  3. 对灵活类型一律使用处理器转换;
  4. 遵循 @type Optional 模式,让调用方永远不用手写@type;
  5. 返回<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-seo

Next SEO is a plug in that makes managing your SEO easier in Next.js projects.

项目地址:https://gitcode.com/gh_mirrors/ne/next-seo
点击查看免费下载
上一篇:immutable-js基准测试:Benchmark.js性能对比
下一篇:Hacker-job 完整指南:利用 AI 智能解析 Hacker News 招聘帖子的 10 个技巧

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询