docs: add JSON-LD schemas to docs (#14007)
This commit is contained in:
@@ -41,7 +41,7 @@
|
||||
}
|
||||
|
||||
*::selection {
|
||||
@apply bg-medusa-bg-highlight;
|
||||
@apply bg-medusa-fg-subtle text-medusa-fg-on-inverted;
|
||||
}
|
||||
|
||||
* {
|
||||
|
||||
@@ -4,13 +4,14 @@ import { BareboneLayout, WideLayout } from "docs-ui"
|
||||
import clsx from "clsx"
|
||||
import { Metadata } from "next"
|
||||
import { inter, robotoMono } from "./fonts"
|
||||
import { config } from "@/config"
|
||||
|
||||
const ogImage =
|
||||
"https://res.cloudinary.com/dza7lstvk/image/upload/v1732200992/Medusa%20Resources/opengraph-image_daq6nx.jpg"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Medusa API Reference",
|
||||
description: "Check out Medusa's API reference",
|
||||
title: `%s - ${config.titleSuffix}`,
|
||||
description: config.description,
|
||||
metadataBase: new URL(
|
||||
process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
|
||||
),
|
||||
|
||||
@@ -5,6 +5,9 @@ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
|
||||
|
||||
export const config: DocsConfig = {
|
||||
...globalConfig,
|
||||
titleSuffix: "Medusa API Reference",
|
||||
description:
|
||||
"Comprehensive reference for Medusa's API routes, request/response structures, authentication methods, and error handling.",
|
||||
baseUrl,
|
||||
basePath: process.env.NEXT_PUBLIC_BASE_PATH,
|
||||
// sidebar is auto generated
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
}
|
||||
|
||||
*::selection {
|
||||
@apply bg-medusa-bg-highlight;
|
||||
@apply bg-medusa-fg-subtle text-medusa-fg-on-inverted;
|
||||
}
|
||||
|
||||
*:not(.code-block-elm) {
|
||||
|
||||
@@ -13,7 +13,7 @@ export const metadata: Metadata = {
|
||||
template: `%s - ${config.titleSuffix}`,
|
||||
default: config.titleSuffix || "",
|
||||
},
|
||||
description: "Explore and learn how to use Medusa.",
|
||||
description: config.description,
|
||||
metadataBase: new URL(
|
||||
process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
|
||||
),
|
||||
|
||||
@@ -8,6 +8,8 @@ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
|
||||
export const config: DocsConfig = {
|
||||
...globalConfig,
|
||||
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,
|
||||
basePath: process.env.NEXT_PUBLIC_BASE_PATH,
|
||||
sidebars: generatedSidebars as Sidebar.Sidebar[],
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
}
|
||||
|
||||
*::selection {
|
||||
@apply bg-medusa-bg-highlight;
|
||||
@apply bg-medusa-fg-subtle text-medusa-fg-on-inverted;
|
||||
}
|
||||
|
||||
*:not(.code-block-elm) {
|
||||
|
||||
@@ -15,8 +15,7 @@ export const metadata: Metadata = {
|
||||
template: `%s - ${config.titleSuffix}`,
|
||||
default: config.titleSuffix || "",
|
||||
},
|
||||
description:
|
||||
"Learn about Cloud, Medusa's PaaS offering for scalable deployments.",
|
||||
description: config.description,
|
||||
metadataBase: new URL(
|
||||
process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
|
||||
),
|
||||
|
||||
@@ -8,6 +8,8 @@ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
|
||||
export const config: DocsConfig = {
|
||||
...globalConfig,
|
||||
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,
|
||||
basePath: process.env.NEXT_PUBLIC_BASE_PATH,
|
||||
sidebars: generatedSidebars as Sidebar.Sidebar[],
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
}
|
||||
|
||||
*::selection {
|
||||
@apply bg-medusa-bg-highlight;
|
||||
@apply bg-medusa-fg-subtle text-medusa-fg-on-inverted;
|
||||
}
|
||||
|
||||
* {
|
||||
|
||||
@@ -15,8 +15,7 @@ export const metadata: Metadata = {
|
||||
template: `%s - ${config.titleSuffix}`,
|
||||
default: config.titleSuffix || "",
|
||||
},
|
||||
description:
|
||||
"Explore Medusa's recipes, API references, configurations, storefront guides, and more.",
|
||||
description: config.description,
|
||||
metadataBase: new URL(
|
||||
process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
|
||||
),
|
||||
|
||||
@@ -7,6 +7,8 @@ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
|
||||
export const config: DocsConfig = {
|
||||
...globalConfig,
|
||||
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,
|
||||
basePath: process.env.NEXT_PUBLIC_BASE_PATH,
|
||||
sidebars: [],
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
}
|
||||
|
||||
*::selection {
|
||||
@apply bg-medusa-bg-highlight;
|
||||
@apply bg-medusa-fg-subtle text-medusa-fg-on-inverted;
|
||||
}
|
||||
|
||||
*:not(.code-block-elm) {
|
||||
|
||||
@@ -15,8 +15,7 @@ export const metadata: Metadata = {
|
||||
template: `%s - ${config.titleSuffix}`,
|
||||
default: config.titleSuffix || "",
|
||||
},
|
||||
description:
|
||||
"Learn about Medusa UI, A React package with primitives for building Medusa applications.",
|
||||
description: config.description,
|
||||
metadataBase: new URL(
|
||||
process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
|
||||
),
|
||||
|
||||
@@ -7,6 +7,8 @@ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
|
||||
export const config: DocsConfig = {
|
||||
...globalConfig,
|
||||
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,
|
||||
basePath: process.env.NEXT_PUBLIC_BASE_PATH,
|
||||
sidebars: generatedSidebars as Sidebar.Sidebar[],
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
}
|
||||
|
||||
*::selection {
|
||||
@apply bg-medusa-bg-highlight;
|
||||
@apply bg-medusa-fg-subtle text-medusa-fg-on-inverted;
|
||||
}
|
||||
|
||||
*:not(.code-block-elm) {
|
||||
|
||||
@@ -15,7 +15,7 @@ export const metadata: Metadata = {
|
||||
template: `%s - ${config.titleSuffix}`,
|
||||
default: config.titleSuffix || "",
|
||||
},
|
||||
description: "Explore and learn how to use the Medusa Admin.",
|
||||
description: config.description,
|
||||
metadataBase: new URL(
|
||||
process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
|
||||
),
|
||||
|
||||
@@ -8,6 +8,8 @@ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
|
||||
export const config: DocsConfig = {
|
||||
...globalConfig,
|
||||
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,
|
||||
basePath: process.env.NEXT_PUBLIC_BASE_PATH,
|
||||
sidebars: generatedSidebars as Sidebar.Sidebar[],
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"eslint": "^9.13.0",
|
||||
"next": "15.3.5",
|
||||
"rimraf": "^5.0.1",
|
||||
"schema-dts": "^1.1.5",
|
||||
"tailwind": "*",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"tsc-alias": "^1.8.7",
|
||||
|
||||
@@ -7,6 +7,8 @@ import { useSidebar, useSiteConfig } from "../../providers"
|
||||
import { Button } from "../Button"
|
||||
import { TriangleRightMini } from "@medusajs/icons"
|
||||
import { Sidebar } from "types"
|
||||
import { getJsonLd } from "../../utils"
|
||||
import type { BreadcrumbList } from "schema-dts"
|
||||
|
||||
type BreadcrumbItems = {
|
||||
title: string
|
||||
@@ -16,7 +18,7 @@ type BreadcrumbItems = {
|
||||
export const Breadcrumbs = () => {
|
||||
const { sidebarHistory, getSidebarFirstLinkChild, getSidebar } = useSidebar()
|
||||
const {
|
||||
config: { breadcrumbOptions },
|
||||
config: { breadcrumbOptions, baseUrl, basePath },
|
||||
} = useSiteConfig()
|
||||
|
||||
const getLinkPath = (item: Sidebar.SidebarItemLink): string => {
|
||||
@@ -51,6 +53,24 @@ export const Breadcrumbs = () => {
|
||||
return items
|
||||
}, [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 (
|
||||
<div
|
||||
className={clsx(
|
||||
@@ -79,6 +99,12 @@ export const Breadcrumbs = () => {
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
))}
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: jsonLd,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import clsx from "clsx"
|
||||
import { RootProviders, Sidebar, SidebarProps } from "@/components"
|
||||
import { MainContentLayout, MainContentLayoutProps } from "./main-content"
|
||||
import { AiAssistantChatWindow } from "../components/AiAssistant/ChatWindow"
|
||||
import { TechArticleJsonLd } from "../components/TechArticleJsonLd"
|
||||
|
||||
export type RootLayoutProps = {
|
||||
bodyClassName?: string
|
||||
@@ -37,6 +38,7 @@ export const RootLayout = ({
|
||||
<MainContentLayout {...mainProps} />
|
||||
<AiAssistantChatWindow />
|
||||
</div>
|
||||
<TechArticleJsonLd />
|
||||
</ProvidersComponent>
|
||||
</RootProviders>
|
||||
</div>
|
||||
|
||||
5
www/packages/docs-ui/src/utils/get-json-ld.ts
Normal file
5
www/packages/docs-ui/src/utils/get-json-ld.ts
Normal 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")
|
||||
}
|
||||
@@ -4,6 +4,7 @@ export * from "./check-sidebar-item-visibility"
|
||||
export * from "./decode-str"
|
||||
export * from "./dom-utils"
|
||||
export * from "./event-parser"
|
||||
export * from "./get-json-ld"
|
||||
export * from "./get-link-with-base-path"
|
||||
export * from "./get-local-search"
|
||||
export * from "./get-navbar-items"
|
||||
|
||||
@@ -9,6 +9,7 @@ export type BreadcrumbOptions = {
|
||||
|
||||
export declare type DocsConfig = {
|
||||
titleSuffix?: string
|
||||
description?: string
|
||||
baseUrl: string
|
||||
basePath?: string
|
||||
sidebars: Sidebar.Sidebar[]
|
||||
|
||||
@@ -7276,6 +7276,7 @@ __metadata:
|
||||
react-uuid: ^2.0.0
|
||||
reodotdev: ^1.0.0
|
||||
rimraf: ^5.0.1
|
||||
schema-dts: ^1.1.5
|
||||
slugify: ^1.6.6
|
||||
tailwind: "*"
|
||||
tailwindcss: ^3.3.3
|
||||
@@ -13652,6 +13653,13 @@ __metadata:
|
||||
languageName: node
|
||||
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":
|
||||
version: 2.16.3
|
||||
resolution: "search-insights@npm:2.16.3"
|
||||
|
||||
Reference in New Issue
Block a user