Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
fixup! Add automatic JSON-LD schema generation for docs
  • Loading branch information
GregHolmes committed Nov 20, 2025
commit 8e2d1823678e35ecf71a1f60bb2ae79d2f75a37c
11 changes: 7 additions & 4 deletions src/components/Head.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,19 @@ export const Head = ({
{keywords && <meta name="keywords" content={keywords} />}

{/* JSON-LD Structured Data */}
{jsonLd &&
(Array.isArray(jsonLd) ? (
{jsonLd && (
Array.isArray(jsonLd) ? (
jsonLd.map((schema, index) => (
<script key={`jsonld-${index}`} type="application/ld+json">
{serializeJsonLd(schema)}
</script>
))
) : (
<script type="application/ld+json">{serializeJsonLd(jsonLd)}</script>
))}
<script type="application/ld+json">
{serializeJsonLd(jsonLd)}
</script>
)
)}

<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="" />
Expand Down
7 changes: 6 additions & 1 deletion src/components/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ export type Frontmatter = {
jsonld_date_modified?: string;
jsonld_author_name?: string;
jsonld_author_type?: string;
[key: string]: unknown; // Allow additional custom JSON-LD fields
jsonld_image?: string;
jsonld_image_description?: string;
jsonld_sdks?: string[];
jsonld_faqs?: Array<{ question: string; answer: string }>;
jsonld_howto_steps?: Array<{ name: string; text: string }>;
[key: string]: unknown;
};

export type PageContextType = {
Expand Down
26 changes: 18 additions & 8 deletions src/components/Layout/MDXWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,23 @@ const MDXWrapper: React.FC<MDXWrapperProps> = ({ children, pageContext, location
// Extract custom JSON-LD fields from frontmatter
const customFields: Record<string, unknown> = {};

// Handle special structured fields
if (frontmatter?.jsonld_image) {
customFields.image = frontmatter.jsonld_image;
}
if (frontmatter?.jsonld_image_description) {
customFields.imageDescription = frontmatter.jsonld_image_description;
}
if (frontmatter?.jsonld_sdks) {
customFields.sdks = frontmatter.jsonld_sdks;
}
if (frontmatter?.jsonld_faqs) {
customFields.faqs = frontmatter.jsonld_faqs;
}
if (frontmatter?.jsonld_howto_steps) {
customFields.howToSteps = frontmatter.jsonld_howto_steps;
}

// Collect any frontmatter fields that start with 'jsonld_custom_'
Object.entries(frontmatter || {}).forEach(([key, value]) => {
if (key.startsWith('jsonld_custom_')) {
Expand Down Expand Up @@ -235,14 +252,7 @@ const MDXWrapper: React.FC<MDXWrapperProps> = ({ children, pageContext, location

return (
<SDKContext.Provider value={{ sdk, setSdk }}>
<Head
title={title}
metaTitle={metaTitle}
canonical={canonical}
description={description}
keywords={keywords}
jsonLd={jsonLd}
/>
<Head title={title} metaTitle={metaTitle} canonical={canonical} description={description} keywords={keywords} jsonLd={jsonLd} />
<Article>
<MarkdownProvider
components={{
Expand Down
35 changes: 35 additions & 0 deletions src/pages/docs/auth/basic.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,41 @@
---
title: Basic auth
meta_description: "Basic authentication allows you to authenticate a secure server using an Ably API key and secret."
meta_keywords: "authentication, api key, basic auth, security"

# Image from the page
jsonld_image: "https://ably.com/docs/images/content/diagrams/Ably-API-Auth1.png"
jsonld_image_description: "Basic authentication process diagram"

# All SDKs shown in code examples
jsonld_sdks:
- javascript
- nodejs
- ruby
- python
- java
- swift
- objc
- csharp
- go
- flutter
- php

# FAQs extracted from "When to use basic auth" section
jsonld_faqs:
- question: "When should I use basic authentication with Ably?"
answer: "Basic authentication is recommended only for server-side use. It should be used when authenticating secure servers with Ably. It's more efficient to use the REST interface rather than realtime when the server is used solely for authentication."
- question: "Why shouldn't I use basic auth on the client side?"
answer: "Basic authentication should not be used client-side because: 1) The secret is passed directly to Ably and can only be used over TLS connections, 2) All configured capabilities of the key are implicitly available which could be abused, 3) Clients can claim any client ID they choose, making client IDs untrustworthy."
- question: "Do I need to use the realtime interface for basic authentication?"
answer: "No, basic authentication is primarily designed for authenticating secure servers, so it's more efficient to use the REST interface of an Ably SDK to avoid the overhead of maintaining a realtime connection."

# HowTo steps for implementing basic auth
jsonld_howto_steps:
- name: "Obtain API key"
text: "Get an API key from your Ably account dashboard"
- name: "Initialize SDK with API key"
text: "Pass the API key when instancing an Ably SDK using the key parameter in ClientOptions"
---

Basic authentication is the simplest way to authenticate with Ably. It requires passing an [API key](/docs/auth#api-key) when instancing an SDK.
Expand Down
149 changes: 135 additions & 14 deletions src/utilities/json-ld.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Generates comprehensive structured data (JSON-LD) using @graph for documentation pages
* to improve discoverability by search engines and AI (LLMs).
*
* Hybrid approach: Uses frontmatter fields + automatic content extraction
* Based on Ably's JSON-LD Schema Prompt requirements.
*/

Expand Down Expand Up @@ -76,7 +77,6 @@ export const generateWebSiteNode = (): JsonLdNode => {
* Generates a BreadcrumbList node for navigation.
*/
export const generateBreadcrumbNode = (pathname: string, url: string): JsonLdNode | null => {
// Parse pathname to create breadcrumbs
const segments = pathname.split('/').filter(Boolean);

if (segments.length === 0) {
Expand All @@ -86,10 +86,8 @@ export const generateBreadcrumbNode = (pathname: string, url: string): JsonLdNod
const breadcrumbs: Array<{ name: string; url: string }> = [];
let currentPath = '';

// Add home
breadcrumbs.push({ name: 'Home', url: 'https://ably.com' });

// Add each segment
segments.forEach((segment) => {
currentPath += `/${segment}`;
const name = segment
Expand Down Expand Up @@ -119,25 +117,113 @@ export const generateBreadcrumbNode = (pathname: string, url: string): JsonLdNod
* Infers the appropriate schema type based on the page URL path.
*/
export const inferSchemaTypeFromPath = (pathname: string): string => {
// API documentation and reference pages
if (pathname.includes('/api/') || pathname.includes('/reference/')) {
return 'APIReference';
}

// Tutorial and guide pages
if (pathname.includes('/guides/') || pathname.includes('/quickstart') || pathname.includes('/getting-started')) {
return 'HowTo';
}

// Conceptual/learning pages
if (pathname.includes('/concepts/') || pathname.includes('/learn/')) {
return 'Article';
}

// Default to TechArticle for technical documentation
return 'TechArticle';
};

/**
* Generates SDK list from frontmatter if provided.
*/
export const generateSDKList = (sdks: string[] | undefined, url: string): JsonLdNode | null => {
if (!sdks || sdks.length === 0) {
return null;
}

const sdkRepos: Record<string, { name: string; repo: string }> = {
javascript: { name: 'JavaScript SDK', repo: 'https://github.com/ably/ably-js' },
nodejs: { name: 'Node.js SDK', repo: 'https://github.com/ably/ably-js' },
ruby: { name: 'Ruby SDK', repo: 'https://github.com/ably/ably-ruby' },
python: { name: 'Python SDK', repo: 'https://github.com/ably/ably-python' },
java: { name: 'Java SDK', repo: 'https://github.com/ably/ably-java' },
swift: { name: 'Swift SDK', repo: 'https://github.com/ably/ably-cocoa' },
objc: { name: 'Objective-C SDK', repo: 'https://github.com/ably/ably-cocoa' },
csharp: { name: 'C# SDK', repo: 'https://github.com/ably/ably-dotnet' },
go: { name: 'Go SDK', repo: 'https://github.com/ably/ably-go' },
flutter: { name: 'Flutter SDK', repo: 'https://github.com/ably/ably-flutter' },
php: { name: 'PHP SDK', repo: 'https://github.com/ably/ably-php' },
};

return {
'@type': 'ItemList',
'@id': `${url}#sdks`,
name: 'Ably SDKs',
itemListElement: sdks.map((sdk, index) => {
const sdkInfo = sdkRepos[sdk.toLowerCase()] || { name: `${sdk} SDK`, repo: '' };
return {
'@type': 'ListItem',
position: index + 1,
item: {
'@type': 'SoftwareSourceCode',
name: sdkInfo.name,
programmingLanguage: sdk,
...(sdkInfo.repo ? { codeRepository: sdkInfo.repo } : {}),
},
};
}),
};
};

/**
* Generates FAQ entities from frontmatter if provided.
*/
export const generateFAQPage = (
faqs: Array<{ question: string; answer: string }> | undefined,
url: string,
): JsonLdNode | null => {
if (!faqs || faqs.length === 0) {
return null;
}

return {
'@type': 'FAQPage',
'@id': `${url}#faq`,
mainEntity: faqs.map((faq) => ({
'@type': 'Question',
name: faq.question,
acceptedAnswer: {
'@type': 'Answer',
text: faq.answer,
},
})),
};
};

/**
* Generates HowTo steps from frontmatter if provided.
*/
export const generateHowToSteps = (
steps: Array<{ name: string; text: string }> | undefined,
url: string,
title: string,
): JsonLdNode | null => {
if (!steps || steps.length === 0) {
return null;
}

return {
'@type': 'HowTo',
'@id': `${url}#howto`,
name: title,
step: steps.map((step, index) => ({
'@type': 'HowToStep',
position: index + 1,
name: step.name,
text: step.text,
})),
};
};

/**
* Generates the main content node (TechArticle, APIReference, HowTo, etc.)
*/
Expand Down Expand Up @@ -174,7 +260,6 @@ export const generateMainContentNode = ({
},
};

// Add optional fields
if (dateModified) {
node.dateModified = dateModified;
}
Expand All @@ -187,9 +272,26 @@ export const generateMainContentNode = ({
node.keywords = keywords.split(',').map((k) => k.trim());
}

// Merge custom fields
// Add image if provided in custom fields
if (customFields.image) {
node.image = {
'@type': 'ImageObject',
url: customFields.image,
...(customFields.imageDescription ? { description: customFields.imageDescription } : {}),
};
}

// Add other custom fields
Object.entries(customFields).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
if (
value !== undefined &&
value !== null &&
key !== 'image' &&
key !== 'imageDescription' &&
key !== 'sdks' &&
key !== 'faqs' &&
key !== 'howToSteps'
) {
node[key] = value;
}
});
Expand All @@ -204,12 +306,10 @@ export const generateMainContentNode = ({
* truthful structured data that improves discoverability.
*/
export const generateCompleteSchema = (params: GenerateSchemaParams): JsonLdSchema => {
const { url, pathname = '', schemaType: explicitSchemaType } = params;
const { url, pathname = '', schemaType: explicitSchemaType, customFields = {} } = params;

// Infer schema type if not explicitly provided
const schemaType = explicitSchemaType || inferSchemaTypeFromPath(pathname);

// Build the @graph array
const graph: JsonLdNode[] = [];

// 1. Always include Ably Organization
Expand All @@ -229,7 +329,28 @@ export const generateCompleteSchema = (params: GenerateSchemaParams): JsonLdSche
// 4. Include main content node
graph.push(generateMainContentNode({ ...params, schemaType }));

// Return complete schema with @graph
// 5. Include SDK list if provided
const sdkList = generateSDKList(customFields.sdks as string[] | undefined, url);
if (sdkList) {
graph.push(sdkList);
}

// 6. Include HowTo steps if provided
const howToSteps = generateHowToSteps(
customFields.howToSteps as Array<{ name: string; text: string }> | undefined,
url,
params.title,
);
if (howToSteps) {
graph.push(howToSteps);
}

// 7. Include FAQPage if provided
const faqPage = generateFAQPage(customFields.faqs as Array<{ question: string; answer: string }> | undefined, url);
if (faqPage) {
graph.push(faqPage);
}

return {
'@context': 'https://schema.org',
'@graph': graph,
Expand Down