@rimelight/seo 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/package.json +7 -8
- package/src/components/AgentDirective.astro +25 -0
- package/src/components/SEOHead.astro +24 -0
- package/src/index.ts +12 -1
- package/src/llms.test.ts +61 -0
- package/src/llms.ts +124 -0
- package/src/schema.test.ts +45 -0
- package/src/schema.ts +95 -0
- package/src/types.ts +19 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rimelight Entertainment
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rimelight/seo",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Rimelight Entertainment's SEO Package",
|
|
6
6
|
"homepage": "https://rimelight.com/docs",
|
|
@@ -30,12 +30,9 @@
|
|
|
30
30
|
"publishConfig": {
|
|
31
31
|
"access": "public"
|
|
32
32
|
},
|
|
33
|
-
"scripts": {
|
|
34
|
-
"check": "pnpm audit --audit-level=moderate && vp check --fix && astro check"
|
|
35
|
-
},
|
|
36
33
|
"devDependencies": {
|
|
37
|
-
"@rimelight/config": "
|
|
38
|
-
"astro": "7.2.
|
|
34
|
+
"@rimelight/config": "0.0.3",
|
|
35
|
+
"astro": "7.2.7",
|
|
39
36
|
"typescript": "6.0.3"
|
|
40
37
|
},
|
|
41
38
|
"peerDependencies": {
|
|
@@ -44,5 +41,7 @@
|
|
|
44
41
|
"engines": {
|
|
45
42
|
"node": ">=26.7.0"
|
|
46
43
|
},
|
|
47
|
-
"
|
|
48
|
-
|
|
44
|
+
"scripts": {
|
|
45
|
+
"check": "vp check --fix && astro check"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
/** Absolute or site-relative URL for this page's markdown version. */
|
|
4
|
+
markdownUrl?: string | undefined
|
|
5
|
+
/** Absolute or site-relative URL for the top-level llms.txt index. */
|
|
6
|
+
llmsUrl?: string | undefined
|
|
7
|
+
class?: string | undefined
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
markdownUrl,
|
|
12
|
+
llmsUrl = "/llms.txt",
|
|
13
|
+
class: className = "sr-only"
|
|
14
|
+
} = Astro.props
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<aside class={className} data-ai-agent-directive>
|
|
18
|
+
This page is available in Markdown format. Markdown is recommended for AI and agentic consumption.
|
|
19
|
+
{markdownUrl && (
|
|
20
|
+
<>
|
|
21
|
+
See <a href={markdownUrl}>{markdownUrl}</a> for this page, or{" "}
|
|
22
|
+
</>
|
|
23
|
+
)}
|
|
24
|
+
<a href={llmsUrl}>{llmsUrl}</a> for the full documentation and content index.
|
|
25
|
+
</aside>
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
buildRobotsMetaContent,
|
|
8
8
|
buildWebSiteSchema
|
|
9
9
|
} from "../meta.js"
|
|
10
|
+
import { buildArticleSchema, buildBreadcrumbSchema } from "../schema.js"
|
|
10
11
|
|
|
11
12
|
const {
|
|
12
13
|
title,
|
|
@@ -23,6 +24,10 @@ const {
|
|
|
23
24
|
charset = 'utf-8',
|
|
24
25
|
viewport = 'width=device-width, initial-scale=1',
|
|
25
26
|
rssFeed,
|
|
27
|
+
articleSchema,
|
|
28
|
+
breadcrumbs,
|
|
29
|
+
markdownUrl,
|
|
30
|
+
versionAlternates = [],
|
|
26
31
|
} = Astro.props as HeadProps
|
|
27
32
|
|
|
28
33
|
// Resolve defaults from config if available
|
|
@@ -85,6 +90,12 @@ const robotsContent = buildRobotsMetaContent({
|
|
|
85
90
|
<link rel="alternate" hreflang={alt.hreflang} href={alt.href} />
|
|
86
91
|
))}
|
|
87
92
|
|
|
93
|
+
<!-- Agent & Version Alternates -->
|
|
94
|
+
{markdownUrl && <link rel="alternate" type="text/markdown" href={markdownUrl} title="Markdown Format" />}
|
|
95
|
+
{versionAlternates.map((alt) => (
|
|
96
|
+
<link rel="alternate" data-version={alt.version} href={alt.href} />
|
|
97
|
+
))}
|
|
98
|
+
|
|
88
99
|
<!-- Open Graph / Facebook -->
|
|
89
100
|
<meta property="og:type" content={ogType} />
|
|
90
101
|
<meta property="og:locale" content={ogLocale} />
|
|
@@ -122,3 +133,16 @@ const robotsContent = buildRobotsMetaContent({
|
|
|
122
133
|
buildWebSiteSchema(config, Astro.site?.toString())
|
|
123
134
|
)} />
|
|
124
135
|
)}
|
|
136
|
+
|
|
137
|
+
{articleSchema && (
|
|
138
|
+
<script type="application/ld+json" is:inline set:html={JSON.stringify(
|
|
139
|
+
buildArticleSchema(articleSchema)
|
|
140
|
+
)} />
|
|
141
|
+
)}
|
|
142
|
+
|
|
143
|
+
{breadcrumbs && breadcrumbs.length > 0 && (
|
|
144
|
+
<script type="application/ld+json" is:inline set:html={JSON.stringify(
|
|
145
|
+
buildBreadcrumbSchema(breadcrumbs)
|
|
146
|
+
)} />
|
|
147
|
+
)}
|
|
148
|
+
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,12 @@ export type {
|
|
|
9
9
|
SiteConfig,
|
|
10
10
|
OGImage,
|
|
11
11
|
RSSFeed,
|
|
12
|
-
HeadProps
|
|
12
|
+
HeadProps,
|
|
13
|
+
LlmsPage,
|
|
14
|
+
LlmsTxtOptions,
|
|
15
|
+
LlmsFullTxtOptions,
|
|
16
|
+
ArticleSchemaOptions,
|
|
17
|
+
BreadcrumbItem
|
|
13
18
|
} from "./types.js"
|
|
14
19
|
|
|
15
20
|
export {
|
|
@@ -28,3 +33,9 @@ export {
|
|
|
28
33
|
buildRobotsMetaContent,
|
|
29
34
|
buildWebSiteSchema
|
|
30
35
|
} from "./meta.js"
|
|
36
|
+
|
|
37
|
+
export { buildLlmsTxt, buildLlmsFullTxt } from "./llms.js"
|
|
38
|
+
export { buildArticleSchema, buildBreadcrumbSchema } from "./schema.js"
|
|
39
|
+
|
|
40
|
+
export { default as SEOHead } from "./components/SEOHead.astro"
|
|
41
|
+
export { default as AgentDirective } from "./components/AgentDirective.astro"
|
package/src/llms.test.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { describe, expect, it } from "vite-plus/test"
|
|
2
|
+
import { buildLlmsTxt, buildLlmsFullTxt } from "./llms.js"
|
|
3
|
+
|
|
4
|
+
describe("seo-llms", () => {
|
|
5
|
+
it("builds standard /llms.txt with sections and pages", () => {
|
|
6
|
+
const output = buildLlmsTxt({
|
|
7
|
+
site: "https://rimelight.com",
|
|
8
|
+
title: "Rimelight Documentation",
|
|
9
|
+
description: "Docs for developers and AI agents.",
|
|
10
|
+
pages: [
|
|
11
|
+
{
|
|
12
|
+
title: "Introduction",
|
|
13
|
+
url: "https://rimelight.com/en/docs/overview/introduction",
|
|
14
|
+
markdownUrl: "https://rimelight.com/en/docs/overview/introduction.md",
|
|
15
|
+
description: "Getting started guide",
|
|
16
|
+
section: "Overview"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
title: "Architecture",
|
|
20
|
+
url: "https://rimelight.com/en/docs/overview/architecture",
|
|
21
|
+
markdownUrl: "https://rimelight.com/en/docs/overview/architecture.md",
|
|
22
|
+
section: "Overview"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
expect(output).toContain("# Rimelight Documentation")
|
|
28
|
+
expect(output).toContain(
|
|
29
|
+
"Full corpus (all pages, one document): https://rimelight.com/llms-full.txt"
|
|
30
|
+
)
|
|
31
|
+
expect(output).toContain("### Overview")
|
|
32
|
+
expect(output).toContain(
|
|
33
|
+
"- [Introduction](https://rimelight.com/en/docs/overview/introduction.md) — Getting started guide"
|
|
34
|
+
)
|
|
35
|
+
expect(output).toContain(
|
|
36
|
+
"- [Architecture](https://rimelight.com/en/docs/overview/architecture.md)"
|
|
37
|
+
)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it("builds full corpus /llms-full.txt", () => {
|
|
41
|
+
const output = buildLlmsFullTxt({
|
|
42
|
+
site: "https://rimelight.com",
|
|
43
|
+
title: "Rimelight Docs",
|
|
44
|
+
pages: [
|
|
45
|
+
{
|
|
46
|
+
title: "Quickstart",
|
|
47
|
+
url: "https://rimelight.com/en/docs/quickstart",
|
|
48
|
+
description: "Step-by-step tutorial",
|
|
49
|
+
version: "v1",
|
|
50
|
+
markdown: "Run `vp dev` to start the development server."
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
expect(output).toContain("# Rimelight Docs — Full Documentation Corpus")
|
|
56
|
+
expect(output).toContain("## Quickstart")
|
|
57
|
+
expect(output).toContain("URL: https://rimelight.com/en/docs/quickstart")
|
|
58
|
+
expect(output).toContain("Version: v1")
|
|
59
|
+
expect(output).toContain("Run `vp dev` to start the development server.")
|
|
60
|
+
})
|
|
61
|
+
})
|
package/src/llms.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
export interface LlmsPage {
|
|
2
|
+
title: string
|
|
3
|
+
url: string
|
|
4
|
+
description?: string
|
|
5
|
+
markdownUrl?: string
|
|
6
|
+
section?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface LlmsTxtOptions {
|
|
10
|
+
site: string
|
|
11
|
+
title: string
|
|
12
|
+
description?: string
|
|
13
|
+
fullCorpusUrl?: string
|
|
14
|
+
pages: LlmsPage[]
|
|
15
|
+
sections?: Array<{ label: string; url: string }>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface LlmsFullTxtOptions {
|
|
19
|
+
site: string
|
|
20
|
+
title: string
|
|
21
|
+
description?: string
|
|
22
|
+
pages: Array<{
|
|
23
|
+
title: string
|
|
24
|
+
url: string
|
|
25
|
+
description?: string
|
|
26
|
+
markdown: string
|
|
27
|
+
version?: string
|
|
28
|
+
}>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Builds standard /llms.txt document format for AI discovery.
|
|
33
|
+
*/
|
|
34
|
+
export function buildLlmsTxt(options: LlmsTxtOptions): string {
|
|
35
|
+
const {
|
|
36
|
+
site,
|
|
37
|
+
title,
|
|
38
|
+
description,
|
|
39
|
+
fullCorpusUrl = `${site.replace(/\/$/, "")}/llms-full.txt`,
|
|
40
|
+
pages,
|
|
41
|
+
sections = []
|
|
42
|
+
} = options
|
|
43
|
+
|
|
44
|
+
const lines = [
|
|
45
|
+
`# ${title}`,
|
|
46
|
+
"",
|
|
47
|
+
description || "Documentation index for AI agents.",
|
|
48
|
+
"",
|
|
49
|
+
`Full corpus (all pages, one document): ${fullCorpusUrl}`,
|
|
50
|
+
""
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
if (sections.length > 0) {
|
|
54
|
+
lines.push("## Sections", "")
|
|
55
|
+
for (const sec of sections) {
|
|
56
|
+
lines.push(`- [${sec.label}](${sec.url})`)
|
|
57
|
+
}
|
|
58
|
+
lines.push("")
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
lines.push("## Pages", "")
|
|
62
|
+
|
|
63
|
+
// Group pages by section if present, or flat
|
|
64
|
+
const grouped = new Map<string, LlmsPage[]>()
|
|
65
|
+
for (const page of pages) {
|
|
66
|
+
const sec = page.section || "General"
|
|
67
|
+
if (!grouped.has(sec)) grouped.set(sec, [])
|
|
68
|
+
grouped.get(sec)!.push(page)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (grouped.size === 1 && grouped.has("General")) {
|
|
72
|
+
for (const page of pages) {
|
|
73
|
+
const pageUrl = page.markdownUrl || page.url
|
|
74
|
+
const desc = page.description ? ` — ${page.description}` : ""
|
|
75
|
+
lines.push(`- [${page.title}](${pageUrl})${desc}`)
|
|
76
|
+
}
|
|
77
|
+
} else {
|
|
78
|
+
for (const [secName, secPages] of grouped.entries()) {
|
|
79
|
+
lines.push(`### ${secName}`, "")
|
|
80
|
+
for (const page of secPages) {
|
|
81
|
+
const pageUrl = page.markdownUrl || page.url
|
|
82
|
+
const desc = page.description ? ` — ${page.description}` : ""
|
|
83
|
+
lines.push(`- [${page.title}](${pageUrl})${desc}`)
|
|
84
|
+
}
|
|
85
|
+
lines.push("")
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
lines.push("")
|
|
90
|
+
return lines.join("\n")
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Builds concatenated /llms-full.txt single corpus document for AI agents.
|
|
95
|
+
*/
|
|
96
|
+
export function buildLlmsFullTxt(options: LlmsFullTxtOptions): string {
|
|
97
|
+
const { title, description, pages } = options
|
|
98
|
+
const parts = [
|
|
99
|
+
`# ${title} — Full Documentation Corpus`,
|
|
100
|
+
"",
|
|
101
|
+
description || "Complete documentation corpus for AI agents.",
|
|
102
|
+
"",
|
|
103
|
+
"---",
|
|
104
|
+
""
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
for (const page of pages) {
|
|
108
|
+
const metaHeader = [
|
|
109
|
+
`## ${page.title}`,
|
|
110
|
+
"",
|
|
111
|
+
`URL: ${page.url}`,
|
|
112
|
+
...(page.description ? [`Description: ${page.description}`] : []),
|
|
113
|
+
...(page.version ? [`Version: ${page.version}`] : []),
|
|
114
|
+
"",
|
|
115
|
+
page.markdown.trim(),
|
|
116
|
+
"",
|
|
117
|
+
"---",
|
|
118
|
+
""
|
|
119
|
+
]
|
|
120
|
+
parts.push(...metaHeader)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return parts.join("\n")
|
|
124
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, it } from "vite-plus/test"
|
|
2
|
+
import { buildArticleSchema, buildBreadcrumbSchema } from "./schema.js"
|
|
3
|
+
|
|
4
|
+
describe("seo-schema", () => {
|
|
5
|
+
it("builds TechArticle JSON-LD schema", () => {
|
|
6
|
+
const schema = buildArticleSchema({
|
|
7
|
+
type: "TechArticle",
|
|
8
|
+
title: "Building on Cloudflare Workers",
|
|
9
|
+
description: "A complete guide to edge rendering",
|
|
10
|
+
url: "https://rimelight.com/blog/cloudflare-workers",
|
|
11
|
+
datePublished: new Date("2026-08-01T12:00:00Z"),
|
|
12
|
+
authorName: "Idan",
|
|
13
|
+
image: "https://rimelight.com/og/article.png"
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
expect(schema["@context"]).toBe("https://schema.org")
|
|
17
|
+
expect(schema["@type"]).toBe("TechArticle")
|
|
18
|
+
expect(schema.headline).toBe("Building on Cloudflare Workers")
|
|
19
|
+
expect(schema.author).toEqual({ "@type": "Person", "name": "Idan" })
|
|
20
|
+
expect(schema.datePublished).toBe("2026-08-01T12:00:00.000Z")
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it("builds BreadcrumbList JSON-LD schema", () => {
|
|
24
|
+
const schema = buildBreadcrumbSchema([
|
|
25
|
+
{ name: "Home", url: "https://rimelight.com" },
|
|
26
|
+
{ name: "Docs", url: "https://rimelight.com/en/docs" },
|
|
27
|
+
{ name: "Installation", url: "https://rimelight.com/en/docs/installation" }
|
|
28
|
+
])
|
|
29
|
+
|
|
30
|
+
expect(schema["@type"]).toBe("BreadcrumbList")
|
|
31
|
+
expect(schema.itemListElement).toHaveLength(3)
|
|
32
|
+
expect(schema.itemListElement[0]).toEqual({
|
|
33
|
+
"@type": "ListItem",
|
|
34
|
+
"position": 1,
|
|
35
|
+
"name": "Home",
|
|
36
|
+
"item": "https://rimelight.com"
|
|
37
|
+
})
|
|
38
|
+
expect(schema.itemListElement[2]).toEqual({
|
|
39
|
+
"@type": "ListItem",
|
|
40
|
+
"position": 3,
|
|
41
|
+
"name": "Installation",
|
|
42
|
+
"item": "https://rimelight.com/en/docs/installation"
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
})
|
package/src/schema.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
export interface ArticleSchemaOptions {
|
|
2
|
+
type?: "TechArticle" | "Article" | "BlogPosting"
|
|
3
|
+
title: string
|
|
4
|
+
description?: string
|
|
5
|
+
url: string
|
|
6
|
+
datePublished?: string | Date
|
|
7
|
+
dateModified?: string | Date
|
|
8
|
+
authorName?: string
|
|
9
|
+
authorUrl?: string
|
|
10
|
+
image?: string
|
|
11
|
+
publisherName?: string
|
|
12
|
+
publisherLogo?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface BreadcrumbItem {
|
|
16
|
+
name: string
|
|
17
|
+
url: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Builds Schema.org Article / TechArticle / BlogPosting JSON-LD.
|
|
22
|
+
*/
|
|
23
|
+
export function buildArticleSchema(options: ArticleSchemaOptions): Record<string, any> {
|
|
24
|
+
const {
|
|
25
|
+
type = "TechArticle",
|
|
26
|
+
title,
|
|
27
|
+
description,
|
|
28
|
+
url,
|
|
29
|
+
datePublished,
|
|
30
|
+
dateModified,
|
|
31
|
+
authorName,
|
|
32
|
+
authorUrl,
|
|
33
|
+
image,
|
|
34
|
+
publisherName = "Rimelight Entertainment",
|
|
35
|
+
publisherLogo
|
|
36
|
+
} = options
|
|
37
|
+
|
|
38
|
+
const schema: Record<string, any> = {
|
|
39
|
+
"@context": "https://schema.org",
|
|
40
|
+
"@type": type,
|
|
41
|
+
"headline": title,
|
|
42
|
+
"mainEntityOfPage": {
|
|
43
|
+
"@type": "WebPage",
|
|
44
|
+
"@id": url
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (description) schema.description = description
|
|
49
|
+
if (image) schema.image = image
|
|
50
|
+
|
|
51
|
+
if (datePublished) {
|
|
52
|
+
schema.datePublished =
|
|
53
|
+
datePublished instanceof Date ? datePublished.toISOString() : datePublished
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (dateModified) {
|
|
57
|
+
schema.dateModified = dateModified instanceof Date ? dateModified.toISOString() : dateModified
|
|
58
|
+
} else if (datePublished) {
|
|
59
|
+
schema.dateModified = schema.datePublished
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (authorName) {
|
|
63
|
+
schema.author = {
|
|
64
|
+
"@type": "Person",
|
|
65
|
+
"name": authorName,
|
|
66
|
+
...(authorUrl ? { url: authorUrl } : {})
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (publisherName) {
|
|
71
|
+
schema.publisher = {
|
|
72
|
+
"@type": "Organization",
|
|
73
|
+
"name": publisherName,
|
|
74
|
+
...(publisherLogo ? { logo: { "@type": "ImageObject", "url": publisherLogo } } : {})
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return schema
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Builds Schema.org BreadcrumbList JSON-LD.
|
|
83
|
+
*/
|
|
84
|
+
export function buildBreadcrumbSchema(items: BreadcrumbItem[]): Record<string, any> {
|
|
85
|
+
return {
|
|
86
|
+
"@context": "https://schema.org",
|
|
87
|
+
"@type": "BreadcrumbList",
|
|
88
|
+
"itemListElement": items.map((item, index) => ({
|
|
89
|
+
"@type": "ListItem",
|
|
90
|
+
"position": index + 1,
|
|
91
|
+
"name": item.name,
|
|
92
|
+
"item": item.url
|
|
93
|
+
}))
|
|
94
|
+
}
|
|
95
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -216,4 +216,23 @@ export interface HeadProps {
|
|
|
216
216
|
* RSS feed configuration
|
|
217
217
|
*/
|
|
218
218
|
rssFeed?: RSSFeed
|
|
219
|
+
/**
|
|
220
|
+
* Article or BlogPosting JSON-LD configuration
|
|
221
|
+
*/
|
|
222
|
+
articleSchema?: import("./schema.js").ArticleSchemaOptions
|
|
223
|
+
/**
|
|
224
|
+
* Breadcrumb list for JSON-LD structured data
|
|
225
|
+
*/
|
|
226
|
+
breadcrumbs?: import("./schema.js").BreadcrumbItem[]
|
|
227
|
+
/**
|
|
228
|
+
* URL for this page's raw Markdown alternate twin
|
|
229
|
+
*/
|
|
230
|
+
markdownUrl?: string
|
|
231
|
+
/**
|
|
232
|
+
* Version alternates for multi-version documentation
|
|
233
|
+
*/
|
|
234
|
+
versionAlternates?: Array<{ version: string; href: string }>
|
|
219
235
|
}
|
|
236
|
+
|
|
237
|
+
export type { LlmsPage, LlmsTxtOptions, LlmsFullTxtOptions } from "./llms.js"
|
|
238
|
+
export type { ArticleSchemaOptions, BreadcrumbItem } from "./schema.js"
|