docs: add JSON-LD schemas to docs (#14007)

This commit is contained in:
Shahed Nasser
2025-11-07 14:43:50 +02:00
committed by GitHub
parent c9648cc9cc
commit dc6f253d31
26 changed files with 146 additions and 17 deletions

View File

@@ -41,7 +41,7 @@
} }
*::selection { *::selection {
@apply bg-medusa-bg-highlight; @apply bg-medusa-fg-subtle text-medusa-fg-on-inverted;
} }
* { * {

View File

@@ -4,13 +4,14 @@ import { BareboneLayout, WideLayout } from "docs-ui"
import clsx from "clsx" import clsx from "clsx"
import { Metadata } from "next" import { Metadata } from "next"
import { inter, robotoMono } from "./fonts" import { inter, robotoMono } from "./fonts"
import { config } from "@/config"
const ogImage = const ogImage =
"https://res.cloudinary.com/dza7lstvk/image/upload/v1732200992/Medusa%20Resources/opengraph-image_daq6nx.jpg" "https://res.cloudinary.com/dza7lstvk/image/upload/v1732200992/Medusa%20Resources/opengraph-image_daq6nx.jpg"
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Medusa API Reference", title: `%s - ${config.titleSuffix}`,
description: "Check out Medusa's API reference", description: config.description,
metadataBase: new URL( metadataBase: new URL(
process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000" process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
), ),

View File

@@ -5,6 +5,9 @@ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
export const config: DocsConfig = { export const config: DocsConfig = {
...globalConfig, ...globalConfig,
titleSuffix: "Medusa API Reference",
description:
"Comprehensive reference for Medusa's API routes, request/response structures, authentication methods, and error handling.",
baseUrl, baseUrl,
basePath: process.env.NEXT_PUBLIC_BASE_PATH, basePath: process.env.NEXT_PUBLIC_BASE_PATH,
// sidebar is auto generated // sidebar is auto generated

View File

@@ -14,7 +14,7 @@
} }
*::selection { *::selection {
@apply bg-medusa-bg-highlight; @apply bg-medusa-fg-subtle text-medusa-fg-on-inverted;
} }
*:not(.code-block-elm) { *:not(.code-block-elm) {

View File

@@ -13,7 +13,7 @@ export const metadata: Metadata = {
template: `%s - ${config.titleSuffix}`, template: `%s - ${config.titleSuffix}`,
default: config.titleSuffix || "", default: config.titleSuffix || "",
}, },
description: "Explore and learn how to use Medusa.", description: config.description,
metadataBase: new URL( metadataBase: new URL(
process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000" process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
), ),

View File

@@ -8,6 +8,8 @@ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
export const config: DocsConfig = { export const config: DocsConfig = {
...globalConfig, ...globalConfig,
titleSuffix: "Medusa Documentation", titleSuffix: "Medusa Documentation",
description:
"Explore and learn how to use Medusa. Learn how to get started, the fundamental concepts, how to customize Medusa, and more.",
baseUrl, baseUrl,
basePath: process.env.NEXT_PUBLIC_BASE_PATH, basePath: process.env.NEXT_PUBLIC_BASE_PATH,
sidebars: generatedSidebars as Sidebar.Sidebar[], sidebars: generatedSidebars as Sidebar.Sidebar[],

View File

@@ -14,7 +14,7 @@
} }
*::selection { *::selection {
@apply bg-medusa-bg-highlight; @apply bg-medusa-fg-subtle text-medusa-fg-on-inverted;
} }
*:not(.code-block-elm) { *:not(.code-block-elm) {

View File

@@ -15,8 +15,7 @@ export const metadata: Metadata = {
template: `%s - ${config.titleSuffix}`, template: `%s - ${config.titleSuffix}`,
default: config.titleSuffix || "", default: config.titleSuffix || "",
}, },
description: description: config.description,
"Learn about Cloud, Medusa's PaaS offering for scalable deployments.",
metadataBase: new URL( metadataBase: new URL(
process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000" process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
), ),

View File

@@ -8,6 +8,8 @@ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
export const config: DocsConfig = { export const config: DocsConfig = {
...globalConfig, ...globalConfig,
titleSuffix: "Medusa Cloud Documentation", titleSuffix: "Medusa Cloud Documentation",
description:
"Learn about Cloud, Medusa's PaaS offering for scalable deployments. Learn how to sign up, deploy, and manage your Medusa Cloud projects.",
baseUrl, baseUrl,
basePath: process.env.NEXT_PUBLIC_BASE_PATH, basePath: process.env.NEXT_PUBLIC_BASE_PATH,
sidebars: generatedSidebars as Sidebar.Sidebar[], sidebars: generatedSidebars as Sidebar.Sidebar[],

View File

@@ -20,7 +20,7 @@
} }
*::selection { *::selection {
@apply bg-medusa-bg-highlight; @apply bg-medusa-fg-subtle text-medusa-fg-on-inverted;
} }
* { * {

View File

@@ -15,8 +15,7 @@ export const metadata: Metadata = {
template: `%s - ${config.titleSuffix}`, template: `%s - ${config.titleSuffix}`,
default: config.titleSuffix || "", default: config.titleSuffix || "",
}, },
description: description: config.description,
"Explore Medusa's recipes, API references, configurations, storefront guides, and more.",
metadataBase: new URL( metadataBase: new URL(
process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000" process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
), ),

View File

@@ -7,6 +7,8 @@ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
export const config: DocsConfig = { export const config: DocsConfig = {
...globalConfig, ...globalConfig,
titleSuffix: "Medusa Documentation", titleSuffix: "Medusa Documentation",
description:
"Explore Medusa's recipes, Commerce and Infrastructure modules, API and SDK references, storefront guides, how-to guides, tutorials, and more.",
baseUrl, baseUrl,
basePath: process.env.NEXT_PUBLIC_BASE_PATH, basePath: process.env.NEXT_PUBLIC_BASE_PATH,
sidebars: [], sidebars: [],

View File

@@ -14,7 +14,7 @@
} }
*::selection { *::selection {
@apply bg-medusa-bg-highlight; @apply bg-medusa-fg-subtle text-medusa-fg-on-inverted;
} }
*:not(.code-block-elm) { *:not(.code-block-elm) {

View File

@@ -15,8 +15,7 @@ export const metadata: Metadata = {
template: `%s - ${config.titleSuffix}`, template: `%s - ${config.titleSuffix}`,
default: config.titleSuffix || "", default: config.titleSuffix || "",
}, },
description: description: config.description,
"Learn about Medusa UI, A React package with primitives for building Medusa applications.",
metadataBase: new URL( metadataBase: new URL(
process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000" process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
), ),

View File

@@ -7,6 +7,8 @@ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
export const config: DocsConfig = { export const config: DocsConfig = {
...globalConfig, ...globalConfig,
titleSuffix: "Medusa UI", titleSuffix: "Medusa UI",
description:
"Learn about Medusa UI, A React package with primitives for building Medusa applications. Explore components, hooks, colors, icons, and more.",
baseUrl, baseUrl,
basePath: process.env.NEXT_PUBLIC_BASE_PATH, basePath: process.env.NEXT_PUBLIC_BASE_PATH,
sidebars: generatedSidebars as Sidebar.Sidebar[], sidebars: generatedSidebars as Sidebar.Sidebar[],

View File

@@ -14,7 +14,7 @@
} }
*::selection { *::selection {
@apply bg-medusa-bg-highlight; @apply bg-medusa-fg-subtle text-medusa-fg-on-inverted;
} }
*:not(.code-block-elm) { *:not(.code-block-elm) {

View File

@@ -15,7 +15,7 @@ export const metadata: Metadata = {
template: `%s - ${config.titleSuffix}`, template: `%s - ${config.titleSuffix}`,
default: config.titleSuffix || "", default: config.titleSuffix || "",
}, },
description: "Explore and learn how to use the Medusa Admin.", description: config.description,
metadataBase: new URL( metadataBase: new URL(
process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000" process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
), ),

View File

@@ -8,6 +8,8 @@ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
export const config: DocsConfig = { export const config: DocsConfig = {
...globalConfig, ...globalConfig,
titleSuffix: "Medusa Admin User Guide", titleSuffix: "Medusa Admin User Guide",
description:
"Explore and learn how to use the Medusa Admin. Learn how to manage products, orders, customers, and more within the Medusa Admin dashboard.",
baseUrl, baseUrl,
basePath: process.env.NEXT_PUBLIC_BASE_PATH, basePath: process.env.NEXT_PUBLIC_BASE_PATH,
sidebars: generatedSidebars as Sidebar.Sidebar[], sidebars: generatedSidebars as Sidebar.Sidebar[],

View File

@@ -41,6 +41,7 @@
"eslint": "^9.13.0", "eslint": "^9.13.0",
"next": "15.3.5", "next": "15.3.5",
"rimraf": "^5.0.1", "rimraf": "^5.0.1",
"schema-dts": "^1.1.5",
"tailwind": "*", "tailwind": "*",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"tsc-alias": "^1.8.7", "tsc-alias": "^1.8.7",

View File

@@ -7,6 +7,8 @@ import { useSidebar, useSiteConfig } from "../../providers"
import { Button } from "../Button" import { Button } from "../Button"
import { TriangleRightMini } from "@medusajs/icons" import { TriangleRightMini } from "@medusajs/icons"
import { Sidebar } from "types" import { Sidebar } from "types"
import { getJsonLd } from "../../utils"
import type { BreadcrumbList } from "schema-dts"
type BreadcrumbItems = { type BreadcrumbItems = {
title: string title: string
@@ -16,7 +18,7 @@ type BreadcrumbItems = {
export const Breadcrumbs = () => { export const Breadcrumbs = () => {
const { sidebarHistory, getSidebarFirstLinkChild, getSidebar } = useSidebar() const { sidebarHistory, getSidebarFirstLinkChild, getSidebar } = useSidebar()
const { const {
config: { breadcrumbOptions }, config: { breadcrumbOptions, baseUrl, basePath },
} = useSiteConfig() } = useSiteConfig()
const getLinkPath = (item: Sidebar.SidebarItemLink): string => { const getLinkPath = (item: Sidebar.SidebarItemLink): string => {
@@ -51,6 +53,24 @@ export const Breadcrumbs = () => {
return items return items
}, [sidebarHistory, breadcrumbOptions]) }, [sidebarHistory, breadcrumbOptions])
const jsonLd = useMemo(() => {
const baseLink = `${baseUrl}${basePath}`.replace(/\/+$/, "")
return getJsonLd<BreadcrumbList>({
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: breadcrumbItems.map((item, index) => ({
"@type": "ListItem",
position: index + 1,
name: item.title,
item: item.link.startsWith("#")
? baseLink
: item.link.startsWith("/")
? `${baseLink}${item.link}`
: item.link,
})),
})
}, [breadcrumbItems, baseUrl, basePath])
return ( return (
<div <div
className={clsx( className={clsx(
@@ -79,6 +99,12 @@ export const Breadcrumbs = () => {
</Button> </Button>
</React.Fragment> </React.Fragment>
))} ))}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: jsonLd,
}}
/>
</div> </div>
) )
} }

View File

@@ -0,0 +1,74 @@
"use client"
import React, { useEffect, useState } from "react"
import type { TechArticle } from "schema-dts"
import { useIsBrowser, useSiteConfig } from "../../providers"
import { getJsonLd } from "../../utils"
import { usePathname } from "next/navigation"
export const TechArticleJsonLd = () => {
const {
config: { baseUrl, basePath, description: configDescription, titleSuffix },
} = useSiteConfig()
const pathname = usePathname()
const { isBrowser } = useIsBrowser()
const [jsonLdData, setJsonLdData] = useState("{}")
useEffect(() => {
if (!isBrowser) {
return
}
// Use a small delay to ensure the document has been updated after navigation
const updateJsonLd = () => {
const baseLink = `${baseUrl}${basePath}`.replace(/\/+$/, "")
const title = document.title.replace(` - ${titleSuffix}`, "")
const description =
document.querySelector("#main p")?.textContent ||
configDescription ||
""
const data = getJsonLd<TechArticle>({
"@context": "https://schema.org",
"@type": "TechArticle",
headline: title,
description,
proficiencyLevel: "Expert",
author: "Medusa",
genre: "Documentation",
keywords: "medusa, ecommerce, open-source",
url: `${baseLink}${pathname}`,
})
setJsonLdData(data)
}
// Update immediately
updateJsonLd()
// Also set up a MutationObserver to watch for title changes
const titleObserver = new MutationObserver(() => {
updateJsonLd()
})
const titleElement = document.querySelector("title")
if (titleElement) {
titleObserver.observe(titleElement, {
childList: true,
characterData: true,
subtree: true,
})
}
return () => {
titleObserver.disconnect()
}
}, [isBrowser, pathname, baseUrl, basePath, configDescription, titleSuffix])
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: jsonLdData }}
/>
)
}

View File

@@ -3,6 +3,7 @@ import clsx from "clsx"
import { RootProviders, Sidebar, SidebarProps } from "@/components" import { RootProviders, Sidebar, SidebarProps } from "@/components"
import { MainContentLayout, MainContentLayoutProps } from "./main-content" import { MainContentLayout, MainContentLayoutProps } from "./main-content"
import { AiAssistantChatWindow } from "../components/AiAssistant/ChatWindow" import { AiAssistantChatWindow } from "../components/AiAssistant/ChatWindow"
import { TechArticleJsonLd } from "../components/TechArticleJsonLd"
export type RootLayoutProps = { export type RootLayoutProps = {
bodyClassName?: string bodyClassName?: string
@@ -37,6 +38,7 @@ export const RootLayout = ({
<MainContentLayout {...mainProps} /> <MainContentLayout {...mainProps} />
<AiAssistantChatWindow /> <AiAssistantChatWindow />
</div> </div>
<TechArticleJsonLd />
</ProvidersComponent> </ProvidersComponent>
</RootProviders> </RootProviders>
</div> </div>

View File

@@ -0,0 +1,5 @@
import type { Thing, WithContext } from "schema-dts"
export function getJsonLd<T extends Thing>(data: WithContext<T>): string {
return JSON.stringify(data, null, 2).replace(/</g, "\\u003c")
}

View File

@@ -4,6 +4,7 @@ export * from "./check-sidebar-item-visibility"
export * from "./decode-str" export * from "./decode-str"
export * from "./dom-utils" export * from "./dom-utils"
export * from "./event-parser" export * from "./event-parser"
export * from "./get-json-ld"
export * from "./get-link-with-base-path" export * from "./get-link-with-base-path"
export * from "./get-local-search" export * from "./get-local-search"
export * from "./get-navbar-items" export * from "./get-navbar-items"

View File

@@ -9,6 +9,7 @@ export type BreadcrumbOptions = {
export declare type DocsConfig = { export declare type DocsConfig = {
titleSuffix?: string titleSuffix?: string
description?: string
baseUrl: string baseUrl: string
basePath?: string basePath?: string
sidebars: Sidebar.Sidebar[] sidebars: Sidebar.Sidebar[]

View File

@@ -7276,6 +7276,7 @@ __metadata:
react-uuid: ^2.0.0 react-uuid: ^2.0.0
reodotdev: ^1.0.0 reodotdev: ^1.0.0
rimraf: ^5.0.1 rimraf: ^5.0.1
schema-dts: ^1.1.5
slugify: ^1.6.6 slugify: ^1.6.6
tailwind: "*" tailwind: "*"
tailwindcss: ^3.3.3 tailwindcss: ^3.3.3
@@ -13652,6 +13653,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"schema-dts@npm:^1.1.5":
version: 1.1.5
resolution: "schema-dts@npm:1.1.5"
checksum: babe23a1577c75c5df79d73acf34af3399e60928eab46f2236a0c4212061f5778d613a31c9e9ec86a2807d20b1ea460673d72d3fe1f64fb7543867460e607f76
languageName: node
linkType: hard
"search-insights@npm:^2.15.0": "search-insights@npm:^2.15.0":
version: 2.16.3 version: 2.16.3
resolution: "search-insights@npm:2.16.3" resolution: "search-insights@npm:2.16.3"