Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions app/[lang]/[[...mdxPath]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { generateStaticParamsFor, importPage } from 'nextra/pages'
import { useMDXComponents } from '../../../mdx-components'
import { PageBreadcrumb } from '../../../components/StructuredData'

export const generateStaticParams = generateStaticParamsFor('mdxPath')

Expand Down Expand Up @@ -30,9 +31,15 @@ export default async function Page(props) {
const params = await props.params
const result = await importPage(params.mdxPath, params.lang)
const { default: MDXContent, toc, metadata } = result
const pathname = params.mdxPath
? `/${params.lang}/${params.mdxPath.join('/')}`
: `/${params.lang}`
return (
<Wrapper toc={toc} metadata={metadata}>
<MDXContent {...props} params={params} />
</Wrapper>
<>
<PageBreadcrumb pathname={pathname} title={metadata?.title as string | undefined} />
<Wrapper toc={toc} metadata={metadata}>
<MDXContent {...props} params={params} />
</Wrapper>
</>
)
}
5 changes: 4 additions & 1 deletion app/[lang]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Head, Search } from 'nextra/components'
import { getPageMap } from 'nextra/page-map'
import Link from 'next/link'
import { PostHogProvider } from '../../components/PostHogProvider'
import { SiteStructuredData } from '../../components/StructuredData'

import 'nextra-theme-docs/style.css'
import '../../styles/globals.css'
Expand Down Expand Up @@ -51,7 +52,9 @@ export default async function LangLayout({ children, params }) {
const { lang } = await params
return (
<html lang={lang} dir="ltr" suppressHydrationWarning>
<Head color={{ hue: 210 }} />
<Head color={{ hue: 210 }}>
<SiteStructuredData />
</Head>
<body>
<PostHogProvider>
<Layout
Expand Down
90 changes: 90 additions & 0 deletions components/StructuredData.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/**
* Server component — renders Schema.org JSON-LD for search engines.
*
* Emitted on every docs page via app/[lang]/layout.tsx:
* - Organization (sitewide)
* - WebSite with SearchAction (enables in-search sitelinks search box)
*
* BreadcrumbList is generated per-page from the pathname; see PageBreadcrumb.
*/

const SITE_URL = 'https://docs.sharpapi.io'
const MAIN_URL = 'https://sharpapi.io'

function jsonLd(obj: object) {
return { __html: JSON.stringify(obj).replace(/</g, '\\u003c') }
}

export function SiteStructuredData() {
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={jsonLd({
'@context': 'https://schema.org',
'@type': 'Organization',
'name': 'SharpAPI',
'url': MAIN_URL,
'logo': `${MAIN_URL}/logo.svg`,
'sameAs': [
'https://github.com/Mlaz-code',
],
})}
/>
<script
type="application/ld+json"
dangerouslySetInnerHTML={jsonLd({
'@context': 'https://schema.org',
'@type': 'WebSite',
'name': 'SharpAPI Docs',
'url': SITE_URL,
'publisher': { '@type': 'Organization', 'name': 'SharpAPI', 'url': MAIN_URL },
})}
/>
</>
)
}

interface PageBreadcrumbProps {
pathname: string
title?: string
}

export function PageBreadcrumb({ pathname, title }: PageBreadcrumbProps) {
const parts = pathname.split('/').filter(Boolean)
if (parts.length === 0)
return null

// Skip the leading locale segment in the user-visible chain.
// Always root the chain at "Docs" → optional subsection → leaf page.
const trail = parts[0]?.length === 2 || parts[0] === 'pt-BR' ? parts.slice(1) : parts
const items: Array<Record<string, unknown>> = [{
'@type': 'ListItem',
'position': 1,
'name': 'Docs',
'item': `${SITE_URL}/${parts[0]}`,
}]
trail.forEach((segment, i) => {
const href = `${SITE_URL}/${parts[0]}/${trail.slice(0, i + 1).join('/')}`
const name = i === trail.length - 1 && title
? title
: segment.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase())
items.push({
'@type': 'ListItem',
'position': i + 2,
'name': name,
'item': href,
})
})

return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={jsonLd({
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
'itemListElement': items,
})}
/>
)
}