Skip to content
Open
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,7 @@ yarn-error.log*

# typescript
*.tsbuildinfo
next-env.d.ts
next-env.d.ts

# Built resources
/public/open-graph/**/*
8 changes: 5 additions & 3 deletions app/docs/[section]/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Link from "next/link";
import { notFound } from 'next/navigation';
import { capitalCase } from 'change-case';
import { getAllArticlePaths, getArticleByPath } from "../../../services/articleService";
import { getMetadata } from "../../../services/metadataService";
import ComingSoon from "../../../components/ComingSoon";
import Markdown from "../../../components/Markdown";

Expand Down Expand Up @@ -35,10 +36,11 @@ export async function generateMetadata({ params }: PageProps) {
const selectedNavItem = navigation.find((item) => item.link.includes(file));
const pageTitle = frontmatter.title || selectedNavItem?.title || section;

return {
return getMetadata({
title: `${pageTitle} - DocumentDB Documentation`,
description: frontmatter.description || undefined,
};
description: frontmatter.description || `${pageTitle} - DocumentDB Documentation`,
pagePath: `docs/${section}${slug.length ? '/' + slug.join('/') : ''}`
});
}

export default async function ArticlePage({ params }: PageProps) {
Expand Down
3 changes: 2 additions & 1 deletion app/docs/reference/[type]/[category]/[name]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ export async function generateMetadata({ params }: { params: Promise<{ type: str
return getMetadata({
title: `${data.frontmatter.title || name} - DocumentDB MQL Reference`,
description: data.frontmatter.description || '',
extraKeywords: ['reference', type, category, name]
extraKeywords: ['reference', type, category, name],
pagePath: `docs/reference/${type}/${category}/${name}`
});
}

Expand Down
3 changes: 2 additions & 1 deletion app/docs/reference/[type]/[category]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export async function generateMetadata({ params }: { params: Promise<{ type: str
return getMetadata({
title: `${title} - DocumentDB MQL Reference`,
description: await sanitizeMarkdown(description),
extraKeywords: ['reference', type, category]
extraKeywords: ['reference', type, category],
pagePath: `docs/reference/${type}/${category}`
});
}

Expand Down
3 changes: 2 additions & 1 deletion app/docs/reference/[type]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export async function generateMetadata({ params }: { params: Promise<{ type: str
return getMetadata({
title: `${title} - DocumentDB MQL Reference`,
description: await sanitizeMarkdown(description),
extraKeywords: ['reference', type]
extraKeywords: ['reference', type],
pagePath: `docs/reference/${type}`
});
}

Expand Down
183 changes: 183 additions & 0 deletions app/services/contentService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { getAllArticlePaths, getArticleByPath } from './articleService';
import { getAllPosts } from './blogService';
import { getReferencesGroupedByTypeAndCategory, getAllReferenceParams, getReferenceByPath } from './referenceService';
import path from 'path';
import { Entity } from '../types/Entity';

export function getAllContent(): Entity[] {
const pages: Entity[] = [];

// 1. Home page
pages.push({
url: '/',
slug: 'home.png',
title: 'DocumentDB',
description: 'A powerful, scalable open-source document database solution',
type: 'home',
});

// 2. Docs landing
pages.push({
url: '/docs',
slug: 'docs.png',
title: 'Documentation',
description: 'Complete DocumentDB documentation and guides',
section: 'docs',
type: 'landing',
});

// 3. All article/documentation pages
const articlePaths = getAllArticlePaths();
for (const articlePath of articlePaths) {
const article = getArticleByPath(articlePath.section, articlePath.slug);
if (article) {
const selectedNavItem = article.navigation.find((item: any) =>
item.link.includes(articlePath.slug[articlePath.slug.length - 1] || 'index')
);
const title = article.frontmatter.title || selectedNavItem?.title || articlePath.section;

let url = `/docs/${articlePath.section}`;
if (articlePath.slug.length > 0) {
url += `/${articlePath.slug.join('/')}`;
}

const mdFilePath = path.join(
process.cwd(),
'articles',
articlePath.section,
...articlePath.slug,
'index.md'
);

// Convert URL to slug filename
const slug = url === '/'
? 'home.png'
: url.slice(1).replace(/\//g, '-') + '.png';

pages.push({
url,
slug,
title,
description: article.frontmatter.description || `${title} - DocumentDB Documentation`,
section: articlePath.section,
type: 'docs',
filePath: mdFilePath,
});
}
}

// 4. Blogs landing
pages.push({
url: '/blogs',
slug: 'blogs.png',
title: 'Blog',
description: 'Latest insights and updates from DocumentDB',
section: 'blog',
type: 'landing',
});

// 5. Individual blog posts (external URIs)
const posts = getAllPosts();
for (const post of posts) {
// Generate slug from title (for external blog posts without slugs)
const slug = post.title
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');

const url = `/blogs/${slug}`;
const filename = url.slice(1).replace(/\//g, '-') + '.png';

pages.push({
url,
slug: filename,
title: post.title,
description: post.description,
section: 'blog',
type: 'blog',
isExternal: true, // Mark as external since these redirect to external URIs
});
}

// 6. Reference landing page
pages.push({
url: '/docs/reference',
slug: 'docs-reference.png',
title: 'API Reference',
description: 'Complete DocumentDB API reference documentation',
section: 'reference',
type: 'landing',
});

// 7. Reference type pages (e.g., /docs/reference/commands)
const referenceContent = getReferencesGroupedByTypeAndCategory();
for (const [type] of Object.entries(referenceContent)) {
const url = `/docs/reference/${type}`;
const slug = url.slice(1).replace(/\//g, '-') + '.png';

pages.push({
url,
slug,
title: `${type.charAt(0).toUpperCase() + type.slice(1)} Reference`,
description: `DocumentDB ${type} reference documentation`,
section: 'reference',
type: 'landing',
});
}

// 8. Reference category pages (e.g., /docs/reference/operators/aggregation)
for (const [type, categories] of Object.entries(referenceContent)) {
for (const [category] of Object.entries(categories)) {
const url = `/docs/reference/${type}/${category}`;
const slug = url.slice(1).replace(/\//g, '-') + '.png';

pages.push({
url,
slug,
title: `${category} - ${type.charAt(0).toUpperCase() + type.slice(1)}`,
description: `${category} ${type} reference documentation`,
section: 'reference',
type: 'landing',
});
}
}

// 9. Individual reference items
const referenceParams = getAllReferenceParams();
for (const param of referenceParams) {
const reference = getReferenceByPath(param.type, param.category, param.name);
if (reference) {
const url = `/docs/reference/${param.type}/${param.category}/${param.name}`;
const slug = url.slice(1).replace(/\//g, '-') + '.png';

const mdFilePath = path.join(
process.cwd(),
'reference',
param.type,
param.category,
`${param.name}.yml`
);

pages.push({
url,
slug,
title: reference.name || param.name,
description: reference.frontmatter.description || `${param.name} - DocumentDB Reference`,
section: 'reference',
type: 'reference',
filePath: mdFilePath,
});
}
}

// 10. Packages page
pages.push({
url: '/packages',
slug: 'packages.png',
title: 'Packages',
description: 'Download and install DocumentDB packages',
type: 'packages',
});

return pages;
}
82 changes: 56 additions & 26 deletions app/services/metadataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,36 +15,66 @@ export const sanitizeMarkdown = async (markdown: string | undefined): Promise<st

return output.trim();
};
import { getBaseUrl, getOpenGraphImageRelativePath } from './siteService';

export const getMetadata = ({ title, description, extraKeywords = [] }: { title: string, description: string, extraKeywords?: string[] }): Metadata => ({
keywords: [...getBaseKeywords(), ...extraKeywords],
title,
description,
openGraph: {
type: 'article',
title,
description,
images: [
{
url: 'https://documentdb.io/images/social-card.png',
}
]
},
twitter: {
card: 'summary_large_image',
interface MetadataParams {
title: string;
description: string;
extraKeywords?: string[];
pagePath?: string;
}

export const getMetadata = ({
title,
description,
extraKeywords = [],
pagePath
}: MetadataParams): Metadata => {
const baseUrl = getBaseUrl();

// Always generate the custom OG image URL
const customOGImage = `${baseUrl}${getImagePath(pagePath)}`;

return {
keywords: [...getBaseKeywords(), ...extraKeywords],
title,
description,
images: [
{
url: 'https://documentdb.io/images/social-card.png',
}
]
},
robots: {
index: true,
follow: true
openGraph: {
type: 'article',
title,
description,
// Array provides implicit fallback - first available image is used
images: [
{ url: customOGImage },
{ url: `${baseUrl}/images/social-card.png` }
]
},
twitter: {
card: 'summary_large_image',
title,
description,
images: [
{ url: customOGImage },
{ url: `${baseUrl}/images/social-card.png` }
]
},
robots: {
index: true,
follow: true
}
};
};

function getImagePath(pagePath?: string): string {
const relativePath = getOpenGraphImageRelativePath();

if (!pagePath) {
return `${relativePath}/home.png`;
}
});

const sanitizedPath = pagePath.replace(/^\//, '').replace(/\//g, '-');
return `${relativePath}/${sanitizedPath}.png`;
}

const getBaseKeywords = (): string[] => [
'DocumentDB',
Expand Down
9 changes: 9 additions & 0 deletions app/services/siteService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Determine base URL for entire site
export function getBaseUrl(): string {
return 'https://documentdb.io';
}

// Determine relative path for Open Graph images
export function getOpenGraphImageRelativePath(): string {
return '/open-graph';
}
Loading