-
-
- DocumentDB
-
+
);
diff --git a/app/data/samples.ts b/app/data/samples.ts
index db8e32e..95951df 100644
--- a/app/data/samples.ts
+++ b/app/data/samples.ts
@@ -3,6 +3,16 @@
import { Sample } from '../types/Sample';
export const samples: Sample[] = [
+ {
+ id: 'hotel-agent-ts',
+ title: 'Hotel Recommendation Agent: RAG with Native Vector Search and LLM Synthesizer in TypeScript',
+ description: 'A TypeScript app that uses DocumentDB OSS native vector search to retrieve semantically similar hotels from a natural-language query, then passes the results to a LlamaIndex synthesizer agent (llama3.2 via Ollama) to generate a concise, comparative recommendation. Runs entirely on open-source tools with no cloud accounts required.',
+ language: 'TypeScript',
+ industry: 'AI/ML',
+ difficulty: 'Intermediate',
+ tags: ['Vector Search', 'AI Agent', 'LlamaIndex', 'Ollama', 'Embeddings', 'RAG', 'DocumentDB OSS', 'Open Source'],
+ githubUrl: 'https://github.com/documentdb/documentdb-samples-gallery/tree/main/hotel-agent-ts',
+ },
{
id: 'book-finder-js',
title: 'BookFinder: AI-Powered Semantic Book Discovery',
@@ -12,5 +22,45 @@ export const samples: Sample[] = [
difficulty: 'Intermediate',
tags: ['Vector Search', 'OpenAI', 'Embeddings', 'Express', 'Semantic Search'],
githubUrl: 'https://github.com/documentdb/documentdb-samples-gallery/tree/main/book-finder-js',
+ },
+ {
+ id: 'retail-product-store-js',
+ title: 'Retail Product Store: Full-Stack Product Catalog with DocumentDB',
+ description: 'A Node.js/Express app backed by DocumentDB that serves a responsive retail storefront with product browsing, category filtering, sort, and keyword search. Products are stored in DocumentDB and served via a REST API, with a vanilla JS frontend.',
+ language: 'Node.js',
+ industry: 'Retail',
+ difficulty: 'Beginner',
+ tags: ['Express', 'REST API', 'DocumentDB OSS', 'Vanilla JS', 'Full Stack'],
+ githubUrl: 'https://github.com/documentdb/documentdb-samples-gallery/tree/main/retail-product-store-js',
+ },
+ {
+ id: 'fraud-detection-agent-py',
+ title: 'Fraud Detection Multi-Agent System: Retrieval, Analysis, and Decision Agents with DocumentDB Vector Search',
+ description: 'A Python app that uses a three-agent pipeline — Retrieval, Analysis, and Decision — to classify transactions as APPROVE, REVIEW, or BLOCK. The Retrieval Agent finds similar historical transactions using DocumentDB OSS native vector search; the Analysis Agent uses llama3.2 via Ollama to identify risk patterns; the Decision Agent issues the final verdict with a confidence score. Runs entirely on open-source tools with no cloud accounts required.',
+ language: 'Python',
+ industry: 'Financial Services',
+ difficulty: 'Intermediate',
+ tags: ['Vector Search', 'Multi-Agent', 'Fraud Detection', 'Ollama', 'Embeddings', 'RAG', 'DocumentDB OSS', 'Open Source'],
+ githubUrl: 'https://github.com/documentdb/documentdb-samples-gallery/tree/main/fraud-detection-agent-py',
+ },
+ {
+ id: 'content-semantic-search-py',
+ title: 'Content Semantic Search Portal: Semantic Search Over Articles, Blogs, and PDFs',
+ description: 'A Python/Flask web portal that stores articles, blogs, and PDF documents as MongoDB documents with vector embeddings in DocumentDB OSS. Users search by meaning using natural language and receive semantically ranked results by cosine similarity. Supports ingesting custom .txt and .pdf files alongside sample content. Built entirely on open-source tools with no cloud accounts required.',
+ language: 'Python',
+ industry: 'Media & Publishing',
+ difficulty: 'Beginner',
+ tags: ['Vector Search', 'Semantic Search', 'Flask', 'Embeddings', 'Ollama', 'PDF', 'DocumentDB OSS', 'Open Source'],
+ githubUrl: 'https://github.com/documentdb/documentdb-samples-gallery/tree/main/content-semantic-search-py',
+ },
+ {
+ id: 'clinical-note-similarity-py',
+ title: 'Clinical Note Similarity Explorer: Find Similar Cases with DocumentDB Vector Search',
+ description: 'A Python/Flask web app that stores de-identified fictional clinical notes with vector embeddings and metadata in DocumentDB OSS. Clinicians and researchers can search for similar cases using natural language clinical descriptions, filtered by medical specialty, with results ranked by semantic similarity. All notes are fictional sample data. Built entirely on open-source tools.',
+ language: 'Python',
+ industry: 'Healthcare',
+ difficulty: 'Intermediate',
+ tags: ['Vector Search', 'Semantic Search', 'Flask', 'Embeddings', 'Ollama', 'Clinical NLP', 'DocumentDB OSS', 'Open Source'],
+ githubUrl: 'https://github.com/documentdb/documentdb-samples-gallery/tree/main/clinical-note-similarity-py',
}
];
diff --git a/app/services/blogService.ts b/app/services/blogService.ts
deleted file mode 100644
index 82c58a8..0000000
--- a/app/services/blogService.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import fs from 'fs';
-import path from 'path';
-import yaml from 'js-yaml';
-import { Post } from '../types/Post';
-
-export function getAllPosts(): Post[] {
- const filePath = path.join(process.cwd(), 'blogs', 'content.yml');
- const fileContents = fs.readFileSync(filePath, 'utf8');
- const posts = yaml.load(fileContents) as Post[];
- return posts;
-}
\ No newline at end of file
diff --git a/app/types/Post.ts b/app/types/Post.ts
deleted file mode 100644
index 23419f0..0000000
--- a/app/types/Post.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export interface Post {
- title: string;
- category: string;
- description: string;
- tags: string[];
- featured?: boolean;
- uri: string;
-}
\ No newline at end of file
diff --git a/blogs/_config.yml b/blogs/_config.yml
new file mode 100644
index 0000000..f9503d1
--- /dev/null
+++ b/blogs/_config.yml
@@ -0,0 +1,22 @@
+title: DocumentDB Blog
+description: Insights, updates, and deep dives into the world of document databases and open-source innovation.
+baseurl: /blogs
+url: ""
+permalink: pretty
+markdown: kramdown
+kramdown:
+ input: GFM
+defaults:
+ -
+ scope:
+ path: ""
+ values:
+ layout: default
+ -
+ scope:
+ path: ""
+ type: posts
+ values:
+ layout: post
+ category: documentdb-blog
+ permalink: /posts/:title/
diff --git a/blogs/_data/categories.yml b/blogs/_data/categories.yml
new file mode 100644
index 0000000..65e72ca
--- /dev/null
+++ b/blogs/_data/categories.yml
@@ -0,0 +1,15 @@
+documentdb-blog:
+ label: DocumentDB Blog
+ meta: Published here
+microsoft-open-source-blog:
+ label: Microsoft Open Source Blog
+ meta: August 25, 2025
+aws-blog:
+ label: AWS Blogs
+ meta: Recent
+azure-cosmos-db-blog:
+ label: Azure Cosmos DB Blog
+ meta: Recent
+yugabytedb-blog:
+ label: YugabyteDB Blog
+ meta: Partner Content
diff --git a/blogs/content.yml b/blogs/_data/posts.yml
similarity index 100%
rename from blogs/content.yml
rename to blogs/_data/posts.yml
diff --git a/blogs/_includes/post-card.html b/blogs/_includes/post-card.html
new file mode 100644
index 0000000..79ffd0f
--- /dev/null
+++ b/blogs/_includes/post-card.html
@@ -0,0 +1,74 @@
+{% assign post = include.post %}
+{% assign category = site.data.categories[post.category] %}
+{% assign description = post.description | default: post.excerpt | strip_html | strip_newlines | strip %}
+
+{% if post.date %}
+ {% assign meta_text = post.date | date: "%B %-d, %Y" %}
+{% elsif post.meta %}
+ {% assign meta_text = post.meta %}
+{% else %}
+ {% assign meta_text = category.meta | default: "Community article" %}
+{% endif %}
+
+{% if post.uri %}
+ {% assign href = post.uri %}
+ {% assign is_external = true %}
+{% else %}
+ {% assign href = post.url | relative_url %}
+ {% assign is_external = false %}
+{% endif %}
+
+
+
+ {% if post.cover_image %}
+ {% if post.cover_image contains '://' %}
+ {% assign cover_image_src = post.cover_image %}
+ {% else %}
+ {% assign cover_image_src = post.cover_image | relative_url %}
+ {% endif %}
+
+

+
+ {% endif %}
+
+
+
+
+
{{ post.title }}
+
{{ description }}
+
+ {% if post.tags and post.tags.size > 0 %}
+
+ {% for tag in post.tags %}
+ {{ tag }}
+ {% endfor %}
+
+ {% endif %}
+
+
+ {% if is_external %}
+ {% if include.featured %}Read full article{% else %}Read more{% endif %}
+ {% else %}
+ {% if include.featured %}Read featured post{% else %}Read post{% endif %}
+ {% endif %}
+ →
+
+
+
+
diff --git a/blogs/_layouts/default.html b/blogs/_layouts/default.html
new file mode 100644
index 0000000..304f61d
--- /dev/null
+++ b/blogs/_layouts/default.html
@@ -0,0 +1,54 @@
+
+
+
+
+
+
{% if page.title %}{{ page.title }} · {% endif %}DocumentDB
+
+
+
+
+
+ {% assign site_root = site.baseurl | replace: '/blogs', '' %}
+ {% if site_root == '' %}
+ {% assign site_root = '/' %}
+ {% endif %}
+
+ {% assign home_href = site_root | append: '/' | replace: '//', '/' %}
+ {% assign docs_href = site_root | append: '/docs/' | replace: '//', '/' %}
+ {% assign packages_href = site_root | append: '/packages/' | replace: '//', '/' %}
+ {% assign operator_href = site_root | append: '/kubernetes-operator/' | replace: '//', '/' %}
+ {% assign logo_href = site_root | append: '/images/DocumentDB Logo - background removed.png' | replace: '//', '/' %}
+ {% assign blogs_href = site.baseurl | append: '/' | replace: '//', '/' %}
+
+
+
+
+
+ {{ content }}
+
+
+
+
+
+
diff --git a/blogs/_layouts/post.html b/blogs/_layouts/post.html
new file mode 100644
index 0000000..13f702b
--- /dev/null
+++ b/blogs/_layouts/post.html
@@ -0,0 +1,53 @@
+---
+layout: default
+---
+{% assign category = site.data.categories[page.category] %}
+
+
+
+ ← Back to all blog posts
+
+
+
+
+ {% if page.cover_image %}
+ {% if page.cover_image contains '://' %}
+ {% assign cover_image_src = page.cover_image %}
+ {% else %}
+ {% assign cover_image_src = page.cover_image | relative_url %}
+ {% endif %}
+
+

+
+ {% endif %}
+
+
+
diff --git a/blogs/_posts/2026-03-19-meet-documentdb-kubernetes-operator.md b/blogs/_posts/2026-03-19-meet-documentdb-kubernetes-operator.md
new file mode 100644
index 0000000..a3c371a
--- /dev/null
+++ b/blogs/_posts/2026-03-19-meet-documentdb-kubernetes-operator.md
@@ -0,0 +1,95 @@
+---
+title: Meet the DocumentDB Kubernetes Operator
+description: Preview an open-source operator that brings declarative deployment, secure connectivity, high availability, backup and restore, and automated multi-cloud deployment to DocumentDB on Kubernetes.
+date: 2026-03-19
+featured: true
+author: DocumentDB team
+category: documentdb-blog
+tags:
+ - Kubernetes
+ - Operator
+ - DocumentDB
+ - Open Source
+---
+The DocumentDB Kubernetes Operator brings MongoDB API compatible document database management to any Kubernetes cluster — with declarative deployment, automatic high availability, backup and restore, TLS, and a documented multi-cloud deployment path across AKS, GKE, and EKS — all fully open source under the MIT license.
+
+[DocumentDB](https://github.com/documentdb/documentdb) is an open-source, MongoDB API compatible document database engine built on PostgreSQL, governed by the Linux Foundation. It powers vCore-based Azure Cosmos DB for MongoDB and provides native support for BSON data types, aggregation pipelines, change streams, vector search, and ACID transactions. The Kubernetes Operator runs and manages DocumentDB on Kubernetes: when you deploy a cluster, it creates and manages PostgreSQL instances, the DocumentDB Gateway, and the supporting Kubernetes resources around them. The gateway translates the MongoDB wire protocol, so existing MongoDB drivers — PyMongo, Node.js, Java, Go, C++ — work without code changes, and teams can keep using familiar clients such as `mongosh`.
+
+## Why this matters
+
+Kubernetes teams have strong patterns for stateless applications, but data services often still depend on custom runbooks and fragile operational glue. The DocumentDB Kubernetes Operator closes that gap for DocumentDB by giving platform teams a single declarative control surface for deployment, availability, security, and recovery.
+
+With the preview release, you can:
+
+- install the operator with Helm
+- have the Helm chart install CloudNativePG as a dependency
+- deploy DocumentDB through a Kubernetes custom resource
+- connect with `mongosh` and other MongoDB API compatible tooling
+- explore a public multi-cloud deployment playground spanning AKS, GKE, and EKS
+- manage backup, restore, TLS, and promotion workflows through Kubernetes-native APIs
+
+## From quickstart to useful data fast
+
+The quickstart is intentionally direct. Install `cert-manager`, install the operator, create a Secret for gateway credentials, and apply a `DocumentDB` resource. The public preview docs target Kubernetes 1.35+ and call out local development with `kind` (v0.31+) and `minikube`, while also showing cloud-friendly access patterns through `LoadBalancer` services on AKS, EKS, and GKE.
+
+At the heart of that flow is a simple custom resource:
+
+```yaml
+apiVersion: documentdb.io/preview
+kind: DocumentDB
+metadata:
+ name: documentdb-preview
+ namespace: documentdb-preview-ns
+spec:
+ nodeCount: 1
+ instancesPerNode: 1
+ documentDbCredentialSecret: documentdb-credentials
+ resource:
+ storage:
+ pvcSize: 10Gi
+ exposeViaService:
+ serviceType: ClusterIP
+```
+
+Once the cluster reports a healthy state, you can connect locally with port forwarding or expose it through a load balancer in supported environments. The result is a much shorter path from cluster creation to a live MongoDB API compatible endpoint.
+
+## Built for day-two operations
+
+Bringing up a cluster is only the beginning, so the operator is opinionated about day-two workflows as well.
+
+Set `instancesPerNode: 3` and the operator creates one primary instance and two replicas for local high availability and automatic failover. Use `Backup` and `ScheduledBackup` resources for on-demand and scheduled backups, retention policies, and restore workflows into a new cluster. And when operators need visibility into what is happening, the `kubectl documentdb` plugin adds purpose-built commands: `status` for cluster-wide inspection, `events` for event triage, and `promote` for controlled primary promotion.
+
+That makes the operator interesting not only for first deployment, but for the operational rhythm that follows: health checks, recovery planning, planned changes, and repeatable workflows that fit naturally into Kubernetes.
+
+## Security and connectivity without reinventing the basics
+
+Secure connectivity is built into the model from the start. The DocumentDB gateway always encrypts client connections; the TLS mode controls how certificates are managed. In practice, that means teams can choose the workflow that matches their environment:
+
+- `SelfSigned` for development and test environments
+- `CertManager` for clusters that already standardize on cert-manager
+- `Provided` for organizations that manage certificates through their own PKI processes
+
+The same pattern carries into networking. The docs cover local development through `ClusterIP` plus port forwarding, and cloud exposure through `LoadBalancer` services where that model makes sense. The operator keeps those choices declarative instead of forcing each team to reinvent them.
+
+## A standout multi-cloud deployment story
+
+The operator already has a public multi-cloud deployment playground spanning AKS, GKE, and EKS. With a single `./deploy.sh` workflow, teams can stand up the infrastructure, join clusters into AKS Fleet, configure Istio multi-cluster service mesh, and prepare the operator for cross-cloud replication. A companion `./deploy-documentdb.sh` workflow then deploys DocumentDB, selects a primary cluster, and outputs connection details plus failover commands.
+
+That is what makes the multi-cloud story worth highlighting: this is not just a vague promise of "runs anywhere," but a concrete, hands-on path to run a MongoDB API compatible database across the three major managed Kubernetes platforms. And for hybrid environments, the repo also includes a KubeFleet-based guide for spanning AKS and an on-premises Kubernetes cluster.
+
+Just as importantly, the operator keeps sensitive workflows explicit. Promotion is an intentional operational action, surfaced through Kubernetes resources and the `kubectl documentdb promote` workflow, which is often exactly the right design when changes cross infrastructure or compliance boundaries.
+
+## Why try it now
+
+The project is in public preview, which makes now the ideal time to evaluate it and influence its direction before general availability. This preview gives you a concrete way to see how DocumentDB fits into your Kubernetes platform: start locally, inspect the declarative control model, test backup and restore, evaluate TLS options, and explore the full operator experience.
+
+The public docs are clear that the operator is not yet recommended for production workloads — but that is exactly why early feedback matters. Try it, push on the edges, and [let the team know](https://github.com/documentdb/documentdb-kubernetes-operator/issues) what you need for GA.
+
+## Start exploring
+
+- [GitHub repository](https://github.com/documentdb/documentdb-kubernetes-operator) — star the project, file issues, and contribute
+- [Quickstart guide](https://github.com/documentdb/documentdb-kubernetes-operator/blob/main/docs/operator-public-documentation/preview/index.md)
+- [Backup and restore guide](https://github.com/documentdb/documentdb-kubernetes-operator/blob/main/docs/operator-public-documentation/preview/backup-and-restore.md)
+- [kubectl-documentdb plugin](https://github.com/documentdb/documentdb-kubernetes-operator/blob/main/docs/operator-public-documentation/preview/kubectl-plugin.md)
+- [TLS configuration guide](https://github.com/documentdb/documentdb-kubernetes-operator/blob/main/docs/operator-public-documentation/preview/configuration/tls.md)
+- [Multi-cloud deployment playground (AKS + GKE + EKS)](https://github.com/documentdb/documentdb-kubernetes-operator/blob/main/documentdb-playground/multi-cloud-deployment/README.md)
diff --git a/blogs/assets/blog.css b/blogs/assets/blog.css
new file mode 100644
index 0000000..596fe49
--- /dev/null
+++ b/blogs/assets/blog.css
@@ -0,0 +1,647 @@
+:root {
+ --page-bg: #09090b;
+ --surface: rgba(23, 23, 27, 0.84);
+ --surface-strong: #111827;
+ --surface-soft: rgba(38, 38, 45, 0.82);
+ --text: #f5f7fb;
+ --muted: #a7afbe;
+ --subtle: #7c8699;
+ --border: rgba(148, 163, 184, 0.16);
+ --shadow: rgba(2, 6, 23, 0.5);
+}
+
+* {
+ box-sizing: border-box;
+}
+
+html {
+ min-height: 100%;
+}
+
+body {
+ margin: 0;
+ min-height: 100%;
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ color: var(--text);
+ background:
+ radial-gradient(circle at top left, rgba(59, 130, 246, 0.18), transparent 30%),
+ radial-gradient(circle at top right, rgba(168, 85, 247, 0.14), transparent 28%),
+ radial-gradient(circle at bottom center, rgba(34, 197, 94, 0.1), transparent 26%),
+ var(--page-bg);
+}
+
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+.site-shell {
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+}
+
+.site-header {
+ position: sticky;
+ top: 0;
+ z-index: 40;
+ border-bottom: 1px solid rgba(64, 64, 64, 0.9);
+ background: rgba(23, 23, 27, 0.94);
+ backdrop-filter: blur(14px);
+ box-shadow: 0 1px 0 rgba(255, 255, 255, 0.02);
+}
+
+.site-header__inner,
+.site-footer__inner {
+ width: min(1280px, calc(100% - 2rem));
+ margin: 0 auto;
+}
+
+.site-header__inner {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1.5rem;
+ min-height: 4rem;
+ padding: 0.5rem 0;
+}
+
+.site-brand {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 1.5rem;
+ font-weight: 700;
+ letter-spacing: -0.02em;
+}
+
+.site-brand__logo {
+ display: block;
+ width: 3rem;
+ height: 3rem;
+}
+
+.site-nav {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 2rem;
+ font-size: 0.95rem;
+ font-weight: 500;
+ color: var(--muted);
+}
+
+.site-nav a {
+ white-space: nowrap;
+ transition: color 160ms ease;
+}
+
+.site-nav a:hover,
+.site-nav a[aria-current="page"] {
+ color: #60a5fa;
+}
+
+.blog-main {
+ flex: 1;
+ padding: 3rem 1rem 5rem;
+}
+
+.blog-hero,
+.posts-group,
+.posts-list,
+.blog-post {
+ width: min(1280px, 100%);
+ margin: 0 auto;
+}
+
+.blog-hero {
+ position: relative;
+ overflow: hidden;
+ text-align: center;
+ margin-bottom: 2.75rem;
+ padding: clamp(2.5rem, 6vw, 4rem) clamp(1.5rem, 4vw, 3rem);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ border-radius: 2rem;
+ background:
+ linear-gradient(180deg, rgba(38, 38, 45, 0.72), rgba(9, 9, 11, 0.9)),
+ radial-gradient(circle at top right, rgba(59, 130, 246, 0.2), transparent 38%),
+ radial-gradient(circle at bottom left, rgba(16, 185, 129, 0.16), transparent 35%);
+ box-shadow: 0 24px 80px -40px rgba(59, 130, 246, 0.45);
+}
+
+.blog-hero::before {
+ content: "";
+ position: absolute;
+ inset: -30%;
+ background:
+ radial-gradient(circle at top right, rgba(59, 130, 246, 0.14), transparent 24%),
+ radial-gradient(circle at bottom left, rgba(16, 185, 129, 0.12), transparent 28%);
+ pointer-events: none;
+}
+
+.blog-hero > * {
+ position: relative;
+}
+
+.blog-hero__kicker {
+ margin: 0 0 0.85rem;
+ font-size: 0.75rem;
+ font-weight: 600;
+ letter-spacing: 0.24em;
+ text-transform: uppercase;
+ color: #93c5fd;
+}
+
+.blog-hero__title {
+ margin: 0;
+ font-size: clamp(2.75rem, 6vw, 4.5rem);
+ line-height: 1.02;
+ letter-spacing: -0.05em;
+}
+
+.blog-hero__description {
+ max-width: 760px;
+ margin: 1.3rem auto 0;
+ font-size: clamp(1.05rem, 2vw, 1.25rem);
+ line-height: 1.7;
+ color: var(--muted);
+}
+
+.posts-group {
+ margin-bottom: 3rem;
+}
+
+.posts-group__header {
+ margin-bottom: 1.1rem;
+}
+
+.posts-group__title {
+ margin: 0;
+ font-size: clamp(1.45rem, 2.2vw, 1.9rem);
+ letter-spacing: -0.03em;
+}
+
+.posts-group__description {
+ margin: 0.45rem 0 0;
+ max-width: 48rem;
+ color: var(--muted);
+ line-height: 1.7;
+}
+
+.posts-list {
+ display: grid;
+ gap: 1.35rem;
+}
+
+.post-card {
+ --accent-rgb: 96, 165, 250;
+ --accent-alt-rgb: 37, 99, 235;
+ display: block;
+ position: relative;
+ overflow: hidden;
+ border: 1px solid var(--border);
+ border-radius: 24px;
+ background: linear-gradient(180deg, rgba(17, 24, 39, 0.88), rgba(10, 10, 11, 0.94));
+ box-shadow: 0 24px 60px -28px var(--shadow);
+ cursor: pointer;
+ transition:
+ transform 180ms ease,
+ border-color 180ms ease,
+ box-shadow 180ms ease,
+ color 180ms ease;
+}
+
+.post-card::before {
+ content: "";
+ position: absolute;
+ inset: -30%;
+ background:
+ radial-gradient(circle at top right, rgba(var(--accent-rgb), 0.22), transparent 24%),
+ radial-gradient(circle at bottom left, rgba(var(--accent-alt-rgb), 0.18), transparent 28%);
+ pointer-events: none;
+}
+
+.post-card:hover,
+.post-card:focus-visible {
+ transform: translateY(-3px);
+ border-color: rgba(var(--accent-rgb), 0.45);
+ box-shadow: 0 30px 65px -28px rgba(var(--accent-rgb), 0.22);
+}
+
+.post-card:focus-visible {
+ outline: 3px solid rgba(var(--accent-rgb), 0.35);
+ outline-offset: 3px;
+}
+
+.post-card__body {
+ position: relative;
+ padding: 1.75rem;
+}
+
+.post-card__media {
+ position: relative;
+ aspect-ratio: 16 / 9;
+ overflow: hidden;
+ border-bottom: 1px solid rgba(148, 163, 184, 0.14);
+ background: rgba(15, 23, 42, 0.8);
+}
+
+.post-card__image {
+ display: block;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.post-card--featured .post-card__media {
+ aspect-ratio: 16 / 8;
+}
+
+.post-card--featured .post-card__body {
+ padding: 2.3rem;
+}
+
+.post-card__header {
+ display: flex;
+ align-items: center;
+ gap: 0.95rem;
+ margin-bottom: 1.25rem;
+}
+
+.post-card__icon {
+ flex: 0 0 auto;
+ width: 2.75rem;
+ height: 2.75rem;
+ border-radius: 0.95rem;
+ background: linear-gradient(135deg, rgba(var(--accent-rgb), 1), rgba(var(--accent-alt-rgb), 1));
+ box-shadow: 0 18px 32px -20px rgba(var(--accent-rgb), 0.85);
+}
+
+.post-card__source,
+.post-card__meta {
+ margin: 0;
+}
+
+.post-card__source {
+ font-size: 0.95rem;
+ font-weight: 600;
+ color: rgb(var(--accent-rgb));
+}
+
+.post-card__meta {
+ margin-top: 0.15rem;
+ color: var(--subtle);
+ font-size: 0.8rem;
+}
+
+.post-card__title {
+ margin: 0;
+ font-size: 1.45rem;
+ line-height: 1.18;
+ letter-spacing: -0.03em;
+ transition: color 180ms ease;
+}
+
+.post-card--featured .post-card__title {
+ font-size: clamp(1.9rem, 3vw, 2.5rem);
+}
+
+.post-card__description {
+ margin: 1rem 0 0;
+ color: var(--muted);
+ line-height: 1.75;
+}
+
+.post-card__tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.65rem;
+ margin-top: 1.4rem;
+}
+
+.post-card__tag {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.45rem 0.8rem;
+ border-radius: 999px;
+ background: rgba(var(--accent-rgb), 0.14);
+ color: rgb(var(--accent-rgb));
+ font-size: 0.8rem;
+ font-weight: 600;
+}
+
+.post-card__link {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin-top: 1.45rem;
+ font-weight: 600;
+ color: rgb(var(--accent-rgb));
+}
+
+.post-card:hover .post-card__title,
+.post-card:focus-visible .post-card__title,
+.post-card:hover .post-card__link,
+.post-card:focus-visible .post-card__link {
+ color: rgb(var(--accent-alt-rgb));
+}
+
+.post-card__link span[aria-hidden="true"] {
+ transition: transform 180ms ease;
+}
+
+.post-card:hover .post-card__link span[aria-hidden="true"],
+.post-card:focus-visible .post-card__link span[aria-hidden="true"] {
+ transform: translateX(3px);
+}
+
+.post-card--microsoft-open-source-blog {
+ --accent-rgb: 96, 165, 250;
+ --accent-alt-rgb: 147, 51, 234;
+}
+
+.post-card--aws-blog {
+ --accent-rgb: 249, 115, 22;
+ --accent-alt-rgb: 245, 158, 11;
+}
+
+.post-card--azure-cosmos-db-blog {
+ --accent-rgb: 168, 85, 247;
+ --accent-alt-rgb: 59, 130, 246;
+}
+
+.post-card--yugabytedb-blog {
+ --accent-rgb: 34, 197, 94;
+ --accent-alt-rgb: 16, 185, 129;
+}
+
+.post-card--documentdb-blog {
+ --accent-rgb: 14, 165, 233;
+ --accent-alt-rgb: 59, 130, 246;
+}
+
+.blog-post {
+ max-width: 840px;
+}
+
+.blog-post__back {
+ margin: 0 0 1.2rem;
+}
+
+.blog-post__back a {
+ color: var(--muted);
+ font-weight: 600;
+ transition: color 160ms ease;
+}
+
+.blog-post__back a:hover {
+ color: var(--text);
+}
+
+.blog-post__header,
+.blog-post__content-shell {
+ border: 1px solid var(--border);
+ border-radius: 28px;
+ background: linear-gradient(180deg, rgba(17, 24, 39, 0.82), rgba(10, 10, 11, 0.9));
+ box-shadow: 0 24px 60px -28px var(--shadow);
+}
+
+.blog-post__header {
+ padding: 2rem;
+}
+
+.blog-post__eyebrow {
+ margin: 0;
+ font-size: 0.92rem;
+ font-weight: 700;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ color: #7dd3fc;
+}
+
+.blog-post__title {
+ margin: 0.75rem 0 0;
+ font-size: clamp(2.4rem, 5vw, 3.8rem);
+ line-height: 1.04;
+ letter-spacing: -0.05em;
+}
+
+.blog-post__meta {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.85rem;
+ margin-top: 1rem;
+ color: var(--subtle);
+ font-size: 0.95rem;
+}
+
+.blog-post__lead {
+ margin: 1rem 0 0;
+ color: var(--muted);
+ font-size: 1.08rem;
+ line-height: 1.75;
+}
+
+.blog-post__tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.65rem;
+ margin-top: 1.35rem;
+}
+
+.blog-post__tag {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.45rem 0.8rem;
+ border-radius: 999px;
+ background: rgba(14, 165, 233, 0.14);
+ color: #7dd3fc;
+ font-size: 0.8rem;
+ font-weight: 600;
+}
+
+.blog-post__content-shell {
+ margin-top: 1.5rem;
+ padding: 2rem;
+}
+
+.blog-post__hero {
+ margin-top: 1.5rem;
+ overflow: hidden;
+ border: 1px solid var(--border);
+ border-radius: 28px;
+ background: rgba(15, 23, 42, 0.85);
+ box-shadow: 0 24px 60px -28px var(--shadow);
+}
+
+.blog-post__hero-image {
+ display: block;
+ width: 100%;
+ height: auto;
+}
+
+.blog-post__content {
+ color: #e5e7eb;
+ line-height: 1.8;
+ font-size: 1.05rem;
+}
+
+.blog-post__content > :first-child {
+ margin-top: 0;
+}
+
+.blog-post__content > :last-child {
+ margin-bottom: 0;
+}
+
+.blog-post__content h2,
+.blog-post__content h3,
+.blog-post__content h4 {
+ margin-top: 2.25rem;
+ margin-bottom: 0.8rem;
+ line-height: 1.2;
+ letter-spacing: -0.03em;
+}
+
+.blog-post__content h2 {
+ font-size: 1.75rem;
+}
+
+.blog-post__content h3 {
+ font-size: 1.35rem;
+}
+
+.blog-post__content p,
+.blog-post__content ul,
+.blog-post__content ol,
+.blog-post__content blockquote,
+.blog-post__content pre,
+.blog-post__content table {
+ margin: 1.2rem 0;
+}
+
+.blog-post__content ul,
+.blog-post__content ol {
+ padding-left: 1.5rem;
+}
+
+.blog-post__content li + li {
+ margin-top: 0.45rem;
+}
+
+.blog-post__content a {
+ color: #93c5fd;
+ text-decoration: underline;
+ text-decoration-color: rgba(147, 197, 253, 0.35);
+ text-underline-offset: 0.18em;
+}
+
+.blog-post__content a:hover {
+ color: #bfdbfe;
+}
+
+.blog-post__content blockquote {
+ padding: 1rem 1.25rem;
+ border-left: 4px solid rgba(59, 130, 246, 0.7);
+ border-radius: 16px;
+ background: rgba(30, 41, 59, 0.55);
+ color: #dbeafe;
+}
+
+.blog-post__content pre {
+ overflow-x: auto;
+ padding: 1rem 1.15rem;
+ border-radius: 18px;
+ border: 1px solid rgba(148, 163, 184, 0.16);
+ background: #0f172a;
+}
+
+.blog-post__content code {
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ font-size: 0.92em;
+}
+
+.blog-post__content :not(pre) > code {
+ padding: 0.18rem 0.42rem;
+ border-radius: 10px;
+ background: rgba(30, 41, 59, 0.75);
+}
+
+.blog-post__content img {
+ display: block;
+ width: 100%;
+ max-width: 100%;
+ margin: 1.5rem 0;
+ border: 1px solid rgba(148, 163, 184, 0.16);
+ border-radius: 24px;
+ background: rgba(15, 23, 42, 0.82);
+ box-shadow: 0 24px 60px -28px var(--shadow);
+}
+
+.blog-post__content table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.blog-post__content th,
+.blog-post__content td {
+ padding: 0.75rem;
+ border-bottom: 1px solid rgba(148, 163, 184, 0.16);
+ text-align: left;
+}
+
+.blog-post__content hr {
+ margin: 2rem 0;
+ border: 0;
+ border-top: 1px solid rgba(148, 163, 184, 0.16);
+}
+
+.site-footer {
+ border-top: 1px solid rgba(64, 64, 64, 0.9);
+ background: rgba(23, 23, 27, 0.96);
+}
+
+.site-footer__inner {
+ padding: 1rem 0;
+ color: var(--subtle);
+ font-size: 0.85rem;
+ line-height: 1.6;
+ text-align: center;
+}
+
+@media (max-width: 820px) {
+ .site-header__inner {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .site-nav {
+ justify-content: flex-start;
+ }
+
+ .blog-main {
+ padding-top: 3rem;
+ }
+
+ .blog-post__header,
+ .blog-post__content-shell {
+ padding: 1.5rem;
+ }
+}
+
+@media (max-width: 640px) {
+ .site-header__inner,
+ .site-footer__inner {
+ width: min(1280px, calc(100% - 1.5rem));
+ }
+
+ .post-card__body,
+ .post-card--featured .post-card__body {
+ padding: 1.35rem;
+ }
+
+ .post-card__header {
+ align-items: flex-start;
+ }
+}
diff --git a/blogs/index.html b/blogs/index.html
new file mode 100644
index 0000000..01d3efb
--- /dev/null
+++ b/blogs/index.html
@@ -0,0 +1,65 @@
+---
+title: Latest from our Blog
+description: Insights, updates, and deep dives into the world of document databases and open-source innovation.
+---
+{% assign featured_local_posts = site.posts | where: "featured", true %}
+{% assign featured_external_posts = site.data.posts | where: "featured", true %}
+{% assign local_posts = site.posts | where_exp: "post", "post.featured != true" %}
+{% assign external_posts = site.data.posts | where_exp: "post", "post.featured != true" %}
+
+
+ Community insights
+ Latest from our Blog
+
+ Insights, updates, and deep dives into the world of document databases and open-source innovation.
+
+
+
+{% if featured_local_posts.size > 0 or featured_external_posts.size > 0 %}
+
+
+
+
+ {% for post in featured_local_posts %}
+ {% include post-card.html post=post featured=true %}
+ {% endfor %}
+
+ {% for post in featured_external_posts %}
+ {% include post-card.html post=post featured=true %}
+ {% endfor %}
+
+
+{% endif %}
+
+{% if local_posts.size > 0 %}
+
+
+
+
+ {% for post in local_posts %}
+ {% include post-card.html post=post featured=false %}
+ {% endfor %}
+
+
+{% endif %}
+
+{% if external_posts.size > 0 %}
+
+
+
+
+ {% for post in external_posts %}
+ {% include post-card.html post=post featured=false %}
+ {% endfor %}
+
+
+{% endif %}
diff --git a/package.json b/package.json
index efb35f2..521141d 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,9 @@
"private": true,
"scripts": {
"dev": "npm run compile && next dev --turbopack",
- "build": "npm run compile && next build --turbopack",
+ "build": "npm run build:next && npm run build:blogs",
+ "build:next": "npm run compile && next build --turbopack",
+ "build:blogs": "bundle exec jekyll build --config blogs/_config.yml --source blogs --destination out/blogs --baseurl \"${JEKYLL_BASE_PATH:-/blogs}\"",
"compile": "npm run compile:clean && npm run compile:content && npm run compile:samples",
"compile:content": "tsx scripts/compile-content.tsx",
"compile:samples": "tsx scripts/compile-samples.tsx",
diff --git a/readme.md b/readme.md
index 422c9e3..1e8cef6 100644
--- a/readme.md
+++ b/readme.md
@@ -1,11 +1,13 @@
# DocumentDB Website
-A modern website for DocumentDB built with [Next.js](https://nextjs.org/) and [Tailwind CSS](https://tailwindcss.com/). The site features community blog posts and technical documentation, with content automatically pulled from the [documentdb/docs](https://github.com/documentdb/docs) repository during the build process.
+A modern website for DocumentDB built with [Next.js](https://nextjs.org/) for the main site and [Jekyll](https://jekyllrb.com/) for the `/blogs/` section. The site features community blog posts and technical documentation, with content automatically pulled from the [documentdb/docs](https://github.com/documentdb/docs) repository during the build process.
## Prerequisites for Development
- **[Node.js](https://nodejs.org/)** (*20 or higher*)
+- **[Ruby](https://www.ruby-lang.org/)** with **Bundler** (*for the Jekyll-powered blog section*)
+
- **[Git](https://git-scm.com/)** (*for cloning documentation content*)
You can develop locally on any machine with these prerequisites installed, or use [GitHub Codespaces](#develop-in-github-codespaces) for a pre-configured environment.
@@ -22,13 +24,26 @@ Get started by cloning and running this repository locally.
npm install
```
-1. Start the development server:
+1. Install Ruby dependencies:
+
+ ```bash
+ bundle install
+ ```
+
+1. Start the Next.js development server:
```bash
npm run dev
```
-1. Observe that the site will be available at http://localhost:3000
+1. For a full static preview, including the Jekyll-powered blog section:
+
+ ```bash
+ npm run build
+ npm run start
+ ```
+
+1. Observe that the preview site will be available at http://localhost:3000
The first time you run `npm run dev` or `npm run build`, documentation content will be automatically compiled from the [documentdb/docs](https://github.com/documentdb/docs) repository.
@@ -40,9 +55,49 @@ We welcome contributions to improve the DocumentDB website, whether it's blog po
Blog posts are managed locally in this repository. Contribute directly through a pull request to this repository.
-1. Open [blogs/content.yml](blogs/content.yml)
+You can publish blog content in two ways:
+
+1. **Markdown posts hosted in this repo**
+
+ Add a new file under `blogs/_posts/` using the Jekyll naming format:
+
+ ```text
+ blogs/_posts/YYYY-MM-DD-my-post-title.md
+ ```
+
+ Start it with front matter like:
+
+ ```yaml
+ ---
+ title: My Blog Post
+ description: One-line summary used in the blog card.
+ date: 2026-03-19
+ featured: false
+ author: Your Name
+ category: documentdb-blog
+ cover_image: /assets/images/posts/my-post-title/hero.png
+ cover_image_alt: Short description of the image
+ tags:
+ - Example
+ - Markdown
+ ---
+ ```
+
+ Store post images under:
+
+ ```text
+ blogs/assets/images/posts/my-post-title/
+ ```
+
+ Then write the post body in Markdown. Jekyll renders the post page and uses the same card layout on the blog index. Images can be referenced directly from Markdown, for example:
+
+ ```md
+ 
+ ```
+
+1. **Curated external articles**
-1. Add your blog post entry following the format of existing posts
+ Open [blogs/_data/posts.yml](blogs/_data/posts.yml) and add a new entry following the existing YAML format when you want the card to link to an article hosted elsewhere.
1. Submit a pull request for review
@@ -100,13 +155,13 @@ The `content.config.json` file controls how documentation is compiled from exter
While content is automatically compiled during builds, you can manually trigger these operations during development:
```bash
-# Clean all target directories
-npm run clean:content
+# Build the full static site artifact
+npm run build
```
```bash
-# Compile content from sources
-npm run compile:content
+# Start the static preview server
+npm run start
```
## Develop in GitHub Codespaces