feat(docs): add SEO metadata to all documentation pages

- Create centralized SEO config (docs-seo.ts) with DOCS_PAGES constants
  and createDocsMetadata helper for DRY metadata generation
- Add JSON-LD schema helpers (docs-schema.ts) for BreadcrumbList,
  TechArticle, HowTo, and WebAPI structured data
- Create JsonLd component for rendering structured data
- Add metadata exports and JSON-LD to all 10 docs pages:
  - Getting Started, Generation, Images, Live URLs, Authentication
  - API Overview, Generations API, Images API, Flows API, Live Scopes API

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Oleg Proskurin 2026-01-01 18:39:39 +07:00
parent 9a6c409906
commit 52649dfb3b
13 changed files with 415 additions and 13 deletions

View File

@ -1,7 +1,11 @@
import type { Metadata } from 'next';
import { TipBox } from '@/components/docs/shared/TipBox';
import { Table } from '@/components/docs/shared/Table';
import { CodeBlock } from '@/components/docs/shared/CodeBlock';
import { DocPage } from '@/components/docs/layout/DocPage';
import { JsonLd } from '@/components/seo/JsonLd';
import { createDocsMetadata, DOCS_PAGES } from '@/config/docs-seo';
import { createBreadcrumbSchema, createTechArticleSchema } from '@/config/docs-schema';
import {
Hero,
SectionHeader,
@ -10,6 +14,19 @@ import {
ResponseBlock,
} from '@/components/docs/blocks';
const PAGE = DOCS_PAGES['api-flows'];
export const metadata: Metadata = createDocsMetadata(PAGE);
const breadcrumbSchema = createBreadcrumbSchema([
{ name: 'Home', path: '/' },
{ name: 'Documentation', path: '/docs/' },
{ name: 'API Reference', path: '/docs/api/' },
{ name: 'Flows', path: '/docs/api/flows/' },
]);
const articleSchema = createTechArticleSchema(PAGE);
const tocItems = [
{ id: 'overview', text: 'Overview', level: 2 },
{ id: 'list-flows', text: 'List Flows', level: 2 },
@ -25,7 +42,10 @@ const tocItems = [
export default function FlowsAPIPage() {
return (
<DocPage
<>
<JsonLd data={breadcrumbSchema} />
<JsonLd data={articleSchema} />
<DocPage
breadcrumbItems={[
{ label: 'Documentation', href: '/docs' },
{ label: 'API Reference', href: '/docs/api' },
@ -328,5 +348,6 @@ export default function FlowsAPIPage() {
</div>
</section>
</DocPage>
</>
);
}

View File

@ -1,7 +1,11 @@
import type { Metadata } from 'next';
import { TipBox } from '@/components/docs/shared/TipBox';
import { Table } from '@/components/docs/shared/Table';
import { CodeBlock } from '@/components/docs/shared/CodeBlock';
import { DocPage } from '@/components/docs/layout/DocPage';
import { JsonLd } from '@/components/seo/JsonLd';
import { createDocsMetadata, DOCS_PAGES } from '@/config/docs-seo';
import { createBreadcrumbSchema, createTechArticleSchema } from '@/config/docs-schema';
import {
Hero,
SectionHeader,
@ -10,6 +14,19 @@ import {
ResponseBlock,
} from '@/components/docs/blocks';
const PAGE = DOCS_PAGES['api-generations'];
export const metadata: Metadata = createDocsMetadata(PAGE);
const breadcrumbSchema = createBreadcrumbSchema([
{ name: 'Home', path: '/' },
{ name: 'Documentation', path: '/docs/' },
{ name: 'API Reference', path: '/docs/api/' },
{ name: 'Generations', path: '/docs/api/generations/' },
]);
const articleSchema = createTechArticleSchema(PAGE);
const tocItems = [
{ id: 'create-generation', text: 'Create Generation', level: 2 },
{ id: 'list-generations', text: 'List Generations', level: 2 },
@ -22,7 +39,10 @@ const tocItems = [
export default function GenerationsAPIPage() {
return (
<DocPage
<>
<JsonLd data={breadcrumbSchema} />
<JsonLd data={articleSchema} />
<DocPage
breadcrumbItems={[
{ label: 'Documentation', href: '/docs' },
{ label: 'API Reference', href: '/docs/api' },
@ -320,5 +340,6 @@ export default function GenerationsAPIPage() {
</div>
</section>
</DocPage>
</>
);
}

View File

@ -1,7 +1,11 @@
import type { Metadata } from 'next';
import { TipBox } from '@/components/docs/shared/TipBox';
import { Table } from '@/components/docs/shared/Table';
import { CodeBlock } from '@/components/docs/shared/CodeBlock';
import { DocPage } from '@/components/docs/layout/DocPage';
import { JsonLd } from '@/components/seo/JsonLd';
import { createDocsMetadata, DOCS_PAGES } from '@/config/docs-seo';
import { createBreadcrumbSchema, createTechArticleSchema } from '@/config/docs-schema';
import {
Hero,
SectionHeader,
@ -10,6 +14,19 @@ import {
ResponseBlock,
} from '@/components/docs/blocks';
const PAGE = DOCS_PAGES['api-images'];
export const metadata: Metadata = createDocsMetadata(PAGE);
const breadcrumbSchema = createBreadcrumbSchema([
{ name: 'Home', path: '/' },
{ name: 'Documentation', path: '/docs/' },
{ name: 'API Reference', path: '/docs/api/' },
{ name: 'Images', path: '/docs/api/images/' },
]);
const articleSchema = createTechArticleSchema(PAGE);
const tocItems = [
{ id: 'upload-image', text: 'Upload Image', level: 2 },
{ id: 'list-images', text: 'List Images', level: 2 },
@ -23,7 +40,10 @@ const tocItems = [
export default function ImagesAPIPage() {
return (
<DocPage
<>
<JsonLd data={breadcrumbSchema} />
<JsonLd data={articleSchema} />
<DocPage
breadcrumbItems={[
{ label: 'Documentation', href: '/docs' },
{ label: 'API Reference', href: '/docs/api' },
@ -355,5 +375,6 @@ curl -X PUT https://api.banatie.app/api/v1/images/550e8400-e29b-41d4-a716-446655
</div>
</section>
</DocPage>
</>
);
}

View File

@ -1,8 +1,11 @@
import type { Metadata } from 'next';
import { TipBox } from '@/components/docs/shared/TipBox';
import { Table } from '@/components/docs/shared/Table';
import { CodeBlock } from '@/components/docs/shared/CodeBlock';
import { DocPage } from '@/components/docs/layout/DocPage';
import { JsonLd } from '@/components/seo/JsonLd';
import { createDocsMetadata, DOCS_PAGES } from '@/config/docs-seo';
import { createBreadcrumbSchema, createTechArticleSchema } from '@/config/docs-schema';
import {
Hero,
SectionHeader,
@ -11,6 +14,19 @@ import {
ResponseBlock,
} from '@/components/docs/blocks';
const PAGE = DOCS_PAGES['api-live-scopes'];
export const metadata: Metadata = createDocsMetadata(PAGE);
const breadcrumbSchema = createBreadcrumbSchema([
{ name: 'Home', path: '/' },
{ name: 'Documentation', path: '/docs/' },
{ name: 'API Reference', path: '/docs/api/' },
{ name: 'Live Scopes', path: '/docs/api/live-scopes/' },
]);
const articleSchema = createTechArticleSchema(PAGE);
const tocItems = [
{ id: 'overview', text: 'Overview', level: 2 },
{ id: 'create-scope', text: 'Create Scope', level: 2 },
@ -25,7 +41,10 @@ const tocItems = [
export default function LiveScopesAPIPage() {
return (
<DocPage
<>
<JsonLd data={breadcrumbSchema} />
<JsonLd data={articleSchema} />
<DocPage
breadcrumbItems={[
{ label: 'Documentation', href: '/docs' },
{ label: 'API Reference', href: '/docs/api' },
@ -414,5 +433,6 @@ curl -X POST https://api.banatie.app/api/v1/live/scopes/hero-section/regenerate
</div>
</section>
</DocPage>
</>
);
}

View File

@ -1,7 +1,11 @@
import type { Metadata } from 'next';
import { TipBox } from '@/components/docs/shared/TipBox';
import { Table } from '@/components/docs/shared/Table';
import { CodeBlock } from '@/components/docs/shared/CodeBlock';
import { DocPage } from '@/components/docs/layout/DocPage';
import { JsonLd } from '@/components/seo/JsonLd';
import { createDocsMetadata, DOCS_PAGES } from '@/config/docs-seo';
import { createBreadcrumbSchema, createTechArticleSchema, API_REFERENCE_SCHEMA } from '@/config/docs-schema';
import {
Hero,
SectionHeader,
@ -10,6 +14,18 @@ import {
LinkCardGrid,
} from '@/components/docs/blocks';
const PAGE = DOCS_PAGES['api-overview'];
export const metadata: Metadata = createDocsMetadata(PAGE);
const breadcrumbSchema = createBreadcrumbSchema([
{ name: 'Home', path: '/' },
{ name: 'Documentation', path: '/docs/' },
{ name: 'API Reference', path: '/docs/api/' },
]);
const articleSchema = createTechArticleSchema(PAGE);
const tocItems = [
{ id: 'base-url', text: 'Base URL', level: 2 },
{ id: 'authentication', text: 'Authentication', level: 2 },
@ -22,7 +38,11 @@ const tocItems = [
export default function APIOverviewPage() {
return (
<DocPage
<>
<JsonLd data={breadcrumbSchema} />
<JsonLd data={articleSchema} />
<JsonLd data={API_REFERENCE_SCHEMA} />
<DocPage
breadcrumbItems={[
{ label: 'Documentation', href: '/docs' },
{ label: 'API Reference' },
@ -233,5 +253,6 @@ export default function APIOverviewPage() {
</LinkCardGrid>
</section>
</DocPage>
</>
);
}

View File

@ -1,13 +1,29 @@
import type { Metadata } from 'next';
import { TipBox } from '@/components/docs/shared/TipBox';
import { Table } from '@/components/docs/shared/Table';
import { CodeBlock } from '@/components/docs/shared/CodeBlock';
import { DocPage } from '@/components/docs/layout/DocPage';
import { JsonLd } from '@/components/seo/JsonLd';
import { createDocsMetadata, DOCS_PAGES } from '@/config/docs-seo';
import { createBreadcrumbSchema, createTechArticleSchema } from '@/config/docs-schema';
import {
Hero,
SectionHeader,
InlineCode,
} from '@/components/docs/blocks';
const PAGE = DOCS_PAGES['authentication'];
export const metadata: Metadata = createDocsMetadata(PAGE);
const breadcrumbSchema = createBreadcrumbSchema([
{ name: 'Home', path: '/' },
{ name: 'Documentation', path: '/docs/' },
{ name: 'Authentication', path: '/docs/authentication/' },
]);
const articleSchema = createTechArticleSchema(PAGE);
const tocItems = [
{ id: 'early-access', text: 'Early Access', level: 2 },
{ id: 'using-your-api-key', text: 'Using Your API Key', level: 2 },
@ -17,7 +33,10 @@ const tocItems = [
export default function AuthenticationPage() {
return (
<DocPage
<>
<JsonLd data={breadcrumbSchema} />
<JsonLd data={articleSchema} />
<DocPage
breadcrumbItems={[
{ label: 'Documentation', href: '/docs' },
{ label: 'Authentication' },
@ -118,5 +137,6 @@ export default function AuthenticationPage() {
</div>
</section>
</DocPage>
</>
);
}

View File

@ -1,8 +1,11 @@
import type { Metadata } from 'next';
import { TipBox } from '@/components/docs/shared/TipBox';
import { Table } from '@/components/docs/shared/Table';
import { CodeBlock } from '@/components/docs/shared/CodeBlock';
import { DocPage } from '@/components/docs/layout/DocPage';
import { JsonLd } from '@/components/seo/JsonLd';
import { createDocsMetadata, DOCS_PAGES } from '@/config/docs-seo';
import { createBreadcrumbSchema, createTechArticleSchema } from '@/config/docs-schema';
import {
Hero,
SectionHeader,
@ -10,6 +13,18 @@ import {
ResponseBlock,
} from '@/components/docs/blocks';
const PAGE = DOCS_PAGES['generation'];
export const metadata: Metadata = createDocsMetadata(PAGE);
const breadcrumbSchema = createBreadcrumbSchema([
{ name: 'Home', path: '/' },
{ name: 'Documentation', path: '/docs/' },
{ name: 'Image Generation', path: '/docs/generation/' },
]);
const articleSchema = createTechArticleSchema(PAGE);
const tocItems = [
{ id: 'basic-generation', text: 'Basic Generation', level: 2 },
{ id: 'aspect-ratios', text: 'Aspect Ratios', level: 2 },
@ -22,7 +37,10 @@ const tocItems = [
export default function GenerationPage() {
return (
<DocPage
<>
<JsonLd data={breadcrumbSchema} />
<JsonLd data={articleSchema} />
<DocPage
breadcrumbItems={[
{ label: 'Documentation', href: '/docs' },
{ label: 'Image Generation' },
@ -258,5 +276,6 @@ export default function GenerationPage() {
</p>
</section>
</DocPage>
</>
);
}

View File

@ -1,6 +1,10 @@
import type { Metadata } from 'next';
import { TipBox } from '@/components/docs/shared/TipBox';
import { CodeBlock } from '@/components/docs/shared/CodeBlock';
import { DocPage } from '@/components/docs/layout/DocPage';
import { JsonLd } from '@/components/seo/JsonLd';
import { createDocsMetadata, DOCS_PAGES } from '@/config/docs-seo';
import { createBreadcrumbSchema, createTechArticleSchema } from '@/config/docs-schema';
import {
Hero,
SectionHeader,
@ -8,6 +12,18 @@ import {
ResponseBlock,
} from '@/components/docs/blocks';
const PAGE = DOCS_PAGES['images'];
export const metadata: Metadata = createDocsMetadata(PAGE);
const breadcrumbSchema = createBreadcrumbSchema([
{ name: 'Home', path: '/' },
{ name: 'Documentation', path: '/docs/' },
{ name: 'Working with Images', path: '/docs/images/' },
]);
const articleSchema = createTechArticleSchema(PAGE);
const tocItems = [
{ id: 'image-urls', text: 'Image URLs', level: 2 },
{ id: 'uploading-images', text: 'Uploading Images', level: 2 },
@ -19,7 +35,10 @@ const tocItems = [
export default function ImagesPage() {
return (
<DocPage
<>
<JsonLd data={breadcrumbSchema} />
<JsonLd data={articleSchema} />
<DocPage
breadcrumbItems={[
{ label: 'Documentation', href: '/docs' },
{ label: 'Working with Images' },
@ -208,5 +227,6 @@ curl https://api.banatie.app/api/v1/images/@brand-logo \\
</div>
</section>
</DocPage>
</>
);
}

View File

@ -1,14 +1,29 @@
import type { Metadata } from 'next';
import { TipBox } from '@/components/docs/shared/TipBox';
import { Table } from '@/components/docs/shared/Table';
import { CodeBlock } from '@/components/docs/shared/CodeBlock';
import { DocPage } from '@/components/docs/layout/DocPage';
import { JsonLd } from '@/components/seo/JsonLd';
import { createDocsMetadata, DOCS_PAGES } from '@/config/docs-seo';
import { createBreadcrumbSchema, createTechArticleSchema } from '@/config/docs-schema';
import {
Hero,
SectionHeader,
InlineCode,
} from '@/components/docs/blocks';
const PAGE = DOCS_PAGES['live-urls'];
export const metadata: Metadata = createDocsMetadata(PAGE);
const breadcrumbSchema = createBreadcrumbSchema([
{ name: 'Home', path: '/' },
{ name: 'Documentation', path: '/docs/' },
{ name: 'Live URLs', path: '/docs/live-urls/' },
]);
const articleSchema = createTechArticleSchema(PAGE);
const tocItems = [
{ id: 'the-concept', text: 'The Concept', level: 2 },
{ id: 'url-format', text: 'URL Format', level: 2 },
@ -22,7 +37,10 @@ const tocItems = [
export default function LiveUrlsPage() {
return (
<DocPage
<>
<JsonLd data={breadcrumbSchema} />
<JsonLd data={articleSchema} />
<DocPage
breadcrumbItems={[
{ label: 'Documentation', href: '/docs' },
{ label: 'Live URLs' },
@ -253,5 +271,6 @@ https://cdn.banatie.app/my-org/my-project/live/blog-images?prompt=...`}
</ul>
</section>
</DocPage>
</>
);
}

View File

@ -1,6 +1,10 @@
import type { Metadata } from 'next';
import { TipBox } from '@/components/docs/shared/TipBox';
import { CodeBlock } from '@/components/docs/shared/CodeBlock';
import { DocPage } from '@/components/docs/layout/DocPage';
import { JsonLd } from '@/components/seo/JsonLd';
import { createDocsMetadata, DOCS_PAGES } from '@/config/docs-seo';
import { createBreadcrumbSchema, createTechArticleSchema, HOW_TO_SCHEMA } from '@/config/docs-schema';
import {
Hero,
SectionHeader,
@ -9,6 +13,17 @@ import {
LinkCardGrid,
} from '@/components/docs/blocks';
const PAGE = DOCS_PAGES['getting-started'];
export const metadata: Metadata = createDocsMetadata(PAGE);
const breadcrumbSchema = createBreadcrumbSchema([
{ name: 'Home', path: '/' },
{ name: 'Documentation', path: '/docs/' },
]);
const articleSchema = createTechArticleSchema(PAGE);
const tocItems = [
{ id: 'what-is-banatie', text: 'What is Banatie?', level: 2 },
{ id: 'your-first-image', text: 'Your First Image', level: 2 },
@ -20,7 +35,11 @@ const tocItems = [
export default function GettingStartedPage() {
return (
<DocPage
<>
<JsonLd data={breadcrumbSchema} />
<JsonLd data={articleSchema} />
<JsonLd data={HOW_TO_SCHEMA} />
<DocPage
breadcrumbItems={[
{ label: 'Documentation', href: '/docs' },
{ label: 'Getting Started' },
@ -189,5 +208,6 @@ export default function GettingStartedPage() {
</div>
</section>
</DocPage>
</>
);
}

View File

@ -0,0 +1,7 @@
type JsonLdProps = {
data: Record<string, unknown>;
};
export const JsonLd = ({ data }: JsonLdProps) => (
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }} />
);

View File

@ -0,0 +1,74 @@
const BASE_URL = 'https://banatie.app';
const ORGANIZATION = {
'@type': 'Organization' as const,
name: 'Banatie',
url: BASE_URL,
};
export type BreadcrumbItem = {
name: string;
path: string;
};
export const createBreadcrumbSchema = (items: BreadcrumbItem[]) => ({
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: items.map((item, index) => ({
'@type': 'ListItem',
position: index + 1,
name: item.name,
item: `${BASE_URL}${item.path}`,
})),
});
export const createTechArticleSchema = (page: { title: string; description: string; path: string }) => ({
'@context': 'https://schema.org',
'@type': 'TechArticle',
headline: page.title,
description: page.description,
author: ORGANIZATION,
publisher: {
...ORGANIZATION,
logo: { '@type': 'ImageObject', url: `${BASE_URL}/logo.png` },
},
mainEntityOfPage: { '@type': 'WebPage', '@id': `${BASE_URL}${page.path}` },
image: `${BASE_URL}/og-image.png`,
});
export const HOW_TO_SCHEMA = {
'@context': 'https://schema.org',
'@type': 'HowTo',
name: 'How to Generate Your First AI Image with Banatie API',
description: 'Generate your first AI image in a few simple steps using Banatie REST API.',
step: [
{
'@type': 'HowToStep',
name: 'Get API Key',
text: 'Sign up at banatie.app to receive your API key within 24 hours.',
},
{
'@type': 'HowToStep',
name: 'Make API Request',
text: 'Send a POST request to /api/v1/generations with your prompt.',
},
{
'@type': 'HowToStep',
name: 'Use Your Image',
text: 'The response contains a CDN URL ready for production use.',
},
],
};
export const API_REFERENCE_SCHEMA = {
'@context': 'https://schema.org',
'@type': 'TechArticle',
headline: 'Banatie API Reference',
description: 'Complete REST API documentation',
about: {
'@type': 'WebAPI',
name: 'Banatie Image Generation API',
documentation: `${BASE_URL}/docs/api/`,
provider: ORGANIZATION,
},
};

View File

@ -0,0 +1,119 @@
import type { Metadata } from 'next';
const BASE_URL = 'https://banatie.app';
export const SEO_DEFAULTS = {
siteName: 'Banatie',
locale: 'en_US',
author: 'Banatie',
ogImage: {
url: `${BASE_URL}/og-image.png`,
width: 1200,
height: 630,
alt: 'Banatie - AI Image Generation API for Developers',
},
};
export type DocsPageSeo = {
path: string;
title: string;
description: string;
keywords: string[];
};
export const createDocsMetadata = (page: DocsPageSeo): Metadata => {
const url = `${BASE_URL}${page.path}`;
return {
title: page.title,
description: page.description,
authors: [{ name: SEO_DEFAULTS.author }],
keywords: page.keywords,
robots: 'index, follow',
alternates: {
canonical: url,
languages: { en: url, 'x-default': url },
},
openGraph: {
title: page.title,
description: page.description,
url,
siteName: SEO_DEFAULTS.siteName,
locale: SEO_DEFAULTS.locale,
type: 'article',
images: [SEO_DEFAULTS.ogImage],
},
twitter: {
card: 'summary_large_image',
title: page.title,
description: page.description,
},
};
};
export const DOCS_PAGES = {
'getting-started': {
path: '/docs/',
title: 'Getting Started with Banatie API | AI Image Generation',
description:
'Generate your first AI image in a few simple steps. REST API for production-ready image assets via CDN.',
keywords: ['banatie api', 'image generation api', 'ai image api tutorial', 'getting started', 'api quickstart'],
},
generation: {
path: '/docs/generation/',
title: 'Image Generation Guide | Banatie API Docs',
description:
'Generate AI images from text prompts. Aspect ratios, prompt templates, reference images, and generation chaining.',
keywords: ['image generation', 'text to image api', 'ai image prompt', 'aspect ratio', 'reference images'],
},
images: {
path: '/docs/images/',
title: 'Working with Images | Banatie API Docs',
description:
'Upload, manage, and organize images. CDN delivery, aliases for easy reference, and image management via API.',
keywords: ['image upload api', 'cdn image delivery', 'image aliases', 'image management', 'organize images'],
},
'live-urls': {
path: '/docs/live-urls/',
title: 'Live URLs — Generate Images from URL | Banatie API Docs',
description:
'Generate images directly from URL parameters. No API calls needed — use the URL in img tags. Automatic caching.',
keywords: ['live url image generation', 'url to image', 'dynamic image generation', 'image from url', 'cached image api'],
},
authentication: {
path: '/docs/authentication/',
title: 'Authentication | Banatie API Docs',
description: 'How to authenticate with Banatie API using API keys. Get your key and start generating images.',
keywords: ['api authentication', 'api key', 'banatie api key', 'x-api-key header'],
},
'api-overview': {
path: '/docs/api/',
title: 'API Reference | Banatie Docs',
description: 'Complete REST API reference for Banatie. All endpoints, parameters, response formats, and error codes.',
keywords: ['banatie api reference', 'rest api documentation', 'api endpoints', 'image generation api docs'],
},
'api-generations': {
path: '/docs/api/generations/',
title: 'Generations API | Banatie API Reference',
description: 'Create, list, update, and delete AI image generations. Full endpoint documentation with examples.',
keywords: ['generations api', 'create generation', 'image generation endpoint', 'regenerate image api'],
},
'api-images': {
path: '/docs/api/images/',
title: 'Images API | Banatie API Reference',
description: 'Upload, list, update, and delete images. Manage aliases and access images via CDN.',
keywords: ['images api', 'upload image api', 'image cdn api', 'image alias api'],
},
'api-flows': {
path: '/docs/api/flows/',
title: 'Flows API | Banatie API Reference',
description: 'Manage generation flows. List, update, and delete flows that group related generations together.',
keywords: ['flows api', 'generation flow', 'chain generations', 'flow management api'],
},
'api-live-scopes': {
path: '/docs/api/live-scopes/',
title: 'Live Scopes API | Banatie API Reference',
description: 'Manage live URL scopes. Create, configure, and control live image generation endpoints.',
keywords: ['live scopes api', 'live url management', 'scope limits', 'live generation api'],
},
} as const satisfies Record<string, DocsPageSeo>;