diff --git a/packages/web/docs/package.json b/packages/web/docs/package.json
index 07afd6a97df..7f3d4d6da1b 100644
--- a/packages/web/docs/package.json
+++ b/packages/web/docs/package.json
@@ -16,7 +16,7 @@
"@radix-ui/react-icons": "1.3.2",
"@radix-ui/react-tabs": "1.1.2",
"@radix-ui/react-tooltip": "1.1.6",
- "@theguild/components": "9.3.4",
+ "@theguild/components": "9.5.0",
"date-fns": "4.1.0",
"next": "15.2.4",
"react": "19.0.0",
diff --git a/packages/web/docs/src/app/blog/(posts)/hive-platform-achieves-soc2-certification/page.mdx b/packages/web/docs/src/app/blog/(posts)/hive-platform-achieves-soc2-certification/page.mdx
index 40d0e5f34c3..ce9771207e4 100644
--- a/packages/web/docs/src/app/blog/(posts)/hive-platform-achieves-soc2-certification/page.mdx
+++ b/packages/web/docs/src/app/blog/(posts)/hive-platform-achieves-soc2-certification/page.mdx
@@ -1,6 +1,6 @@
---
title: Hive Platform Achieves SOC-2 Type II Certification
-tags: [security, cloud, hive, platform, compliance]
+tags: [security, cloud, graphql-hive, platform, compliance]
authors: dotan
date: 2025-03-25
description:
diff --git a/packages/web/docs/src/app/blog/(posts)/layout.tsx b/packages/web/docs/src/app/blog/(posts)/layout.tsx
index ff4c4e49795..b7ff0c6576e 100644
--- a/packages/web/docs/src/app/blog/(posts)/layout.tsx
+++ b/packages/web/docs/src/app/blog/(posts)/layout.tsx
@@ -1,7 +1,8 @@
-import { cn, HiveLayoutConfig } from '@theguild/components';
+import { cn, GetYourAPIGameRightSection, HiveLayoutConfig } from '@theguild/components';
import { LandingPageContainer } from '../../../components/landing-page-container';
-import '../../hive-prose-styles.css';
import { BlogPostHeader } from '../components/blog-post-layout/blog-post-header';
+import { SimilarPosts } from '../components/blog-post-layout/similar-posts';
+import '../../hive-prose-styles.css';
const MAIN_CONTENT = 'main-content';
@@ -13,11 +14,13 @@ export default function BlogPostLayout({ children }: { children: React.ReactNode
div>:first-child]:hidden [&_main>p:first-of-type]:text-2xl/8',
+ 'mx-auto flex *:!pl-2 sm:*:!ml-auto sm:*:!pl-0 [&>div>:first-child]:hidden [&_main>p:first-of-type]:text-xl/8 md:[&_main>p:first-of-type]:text-2xl/8',
)}
>
{children}
+
+
);
}
diff --git a/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-header.tsx b/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-header.tsx
index c118ba51811..7b1e1dc6566 100644
--- a/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-header.tsx
+++ b/packages/web/docs/src/app/blog/components/blog-post-layout/blog-post-header.tsx
@@ -20,7 +20,7 @@ export function BlogPostHeader({ className }: { className?: string }) {
{image && }
{title}
@@ -47,7 +47,7 @@ export function BlogPostHeader({ className }: { className?: string }) {
authors: Array.isArray(authors) ? authors : [authors],
date,
}}
- className="mt-4"
+ className="mt-4 max-sm:justify-start"
/>
>
diff --git a/packages/web/docs/src/app/blog/components/blog-post-layout/similar-posts/client.tsx b/packages/web/docs/src/app/blog/components/blog-post-layout/similar-posts/client.tsx
new file mode 100644
index 00000000000..5869f5bdd83
--- /dev/null
+++ b/packages/web/docs/src/app/blog/components/blog-post-layout/similar-posts/client.tsx
@@ -0,0 +1,37 @@
+'use client';
+
+import { useFrontmatter } from '#components/use-frontmatter';
+import { BlogFrontmatter, BlogPostFile } from '../../../blog-types';
+import { BlogCard } from '../../blog-card';
+
+export function SimilarPostsClient({ sortedPosts }: { sortedPosts: BlogPostFile[] }) {
+ const { frontmatter } = useFrontmatter();
+ const tags = Array.isArray(frontmatter.tags) ? frontmatter.tags : [frontmatter.tags];
+
+ const postsToShow = [];
+ const intersectedTags: string[] = [];
+
+ for (let i = 0; i < sortedPosts.length && postsToShow.length < 2; i++) {
+ const post = sortedPosts[i];
+ if (post.frontMatter.title !== frontmatter.title) {
+ const postTags = Array.isArray(post.frontMatter.tags)
+ ? post.frontMatter.tags
+ : [post.frontMatter.tags];
+
+ const tagsInCommon = postTags.filter(tag => tags.includes(tag));
+ if (tagsInCommon.length > 0) {
+ postsToShow.push(post);
+ intersectedTags.push(tagsInCommon[0]);
+ }
+ }
+ }
+
+ if (postsToShow.length === 0) {
+ // We need the CSS in the server component to know we didn't find any similar posts.
+ return ;
+ }
+
+ return postsToShow.map((post, i) => (
+
+ ));
+}
diff --git a/packages/web/docs/src/app/blog/components/blog-post-layout/similar-posts/index.tsx b/packages/web/docs/src/app/blog/components/blog-post-layout/similar-posts/index.tsx
new file mode 100644
index 00000000000..e82ee60c78b
--- /dev/null
+++ b/packages/web/docs/src/app/blog/components/blog-post-layout/similar-posts/index.tsx
@@ -0,0 +1,43 @@
+import { ArrowIcon, cn, Heading } from '@theguild/components';
+import { getPageMap } from '@theguild/components/server';
+import { isBlogPost } from '../../../blog-types';
+import { SimilarPostsClient } from './client';
+
+export async function SimilarPosts({ className }: { className?: string }) {
+ // We're overfetching all posts here, because Nextra doesn't allow us
+ // to get the current post frontmatter on the server, and we need it to
+ // filter the similar posts. This could be worked around by moving the
+ // code to a remark/rehype plugin layer, but it seems like an overkill.
+ // We can optimize this later.
+ const [_meta, _indexPage, ...pageMap] = await getPageMap('/blog');
+ const sortedPosts = pageMap
+ .filter(isBlogPost)
+ .sort((a, b) => new Date(b.frontMatter.date).getTime() - new Date(a.frontMatter.date).getTime())
+ .slice(
+ 0,
+ // This is an assumption that 100 posts is enough to find similar ones.
+ // Worst case we'll not render the section if there's no similar post.
+ 100,
+ );
+
+ return (
+
+
+
+
Dive deeper into related topics.
+
+
+
+ );
+}
diff --git a/packages/web/docs/src/app/gateway/lets-get-advanced-section.tsx b/packages/web/docs/src/app/gateway/lets-get-advanced-section.tsx
index 5f543aba425..1957c5d8a8d 100644
--- a/packages/web/docs/src/app/gateway/lets-get-advanced-section.tsx
+++ b/packages/web/docs/src/app/gateway/lets-get-advanced-section.tsx
@@ -15,6 +15,7 @@ export function LetsGetAdvancedSection({ className, ...rest }: React.HTMLAttribu
}
heading="GraphQL Subscriptions"
// moved to the last place, because it's the shortest
@@ -33,7 +34,7 @@ export function LetsGetAdvancedSection({ className, ...rest }: React.HTMLAttribu
Documentation
- } heading="@defer and @stream Support">
+ } heading="@defer and @stream Support">
Allows more efficient data loading patterns, improving user interface responsiveness and
system performance.
@@ -45,7 +46,7 @@ export function LetsGetAdvancedSection({ className, ...rest }: React.HTMLAttribu
Documentation
- } heading="Request Batching">
+ } heading="Request Batching">
Reduces network overhead by enabling multiple GraphQL operations in a single HTTP
request, enhancing data retrieval efficiency.
@@ -57,7 +58,7 @@ export function LetsGetAdvancedSection({ className, ...rest }: React.HTMLAttribu
Documentation
- } heading="Demand Control">
+ } heading="Demand Control">
Facilitates efficient management of API resources by setting limits on query complexity
and execution depth, tailored for high-demand cloud environments.
diff --git a/packages/web/docs/src/app/layout.tsx b/packages/web/docs/src/app/layout.tsx
index e0b19678c69..f6ae1f45a33 100644
--- a/packages/web/docs/src/app/layout.tsx
+++ b/packages/web/docs/src/app/layout.tsx
@@ -97,11 +97,7 @@ export default async function HiveDocsLayout({ children }: { children: ReactNode
icon: ,
children: 'Case Studies',
},
- {
- href: 'https://the-guild.dev/graphql/hive/blog',
- icon: ,
- children: 'Blog',
- },
+ { href: '/blog', icon: , children: 'Blog' },
{
href: 'https://github.com/graphql-hive/console',
icon: ,
diff --git a/packages/web/docs/src/components/lede.tsx b/packages/web/docs/src/components/lede.tsx
index 04740b8641b..f7944abc16e 100644
--- a/packages/web/docs/src/components/lede.tsx
+++ b/packages/web/docs/src/components/lede.tsx
@@ -3,5 +3,5 @@ import { cn } from '@theguild/components';
export interface LedeProps extends React.HTMLAttributes {}
export function Lede(props: LedeProps) {
- return ;
+ return ;
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 903ea48543e..b1fa2f45ba8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -2037,8 +2037,8 @@ importers:
specifier: 1.1.6
version: 1.1.6(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@theguild/components':
- specifier: 9.3.4
- version: 9.3.4(@theguild/tailwind-config@0.6.3(postcss-import@16.1.0(postcss@8.4.49))(postcss-lightningcss@1.0.1(postcss@8.4.49))(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.3))))(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(immer@10.1.1)(next@15.2.4(@babel/core@7.22.9)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)(use-sync-external-store@1.2.0(react@19.0.0))
+ specifier: 9.5.0
+ version: 9.5.0(@theguild/tailwind-config@0.6.3(postcss-import@16.1.0(postcss@8.4.49))(postcss-lightningcss@1.0.1(postcss@8.4.49))(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.3))))(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(immer@10.1.1)(next@15.2.4(@babel/core@7.22.9)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)(use-sync-external-store@1.2.0(react@19.0.0))
date-fns:
specifier: 4.1.0
version: 4.1.0
@@ -3512,6 +3512,7 @@ packages:
'@fastify/vite@6.0.7':
resolution: {integrity: sha512-+dRo9KUkvmbqdmBskG02SwigWl06Mwkw8SBDK1zTNH6vd4DyXbRvI7RmJEmBkLouSU81KTzy1+OzwHSffqSD6w==}
+ bundledDependencies: []
'@floating-ui/core@1.2.6':
resolution: {integrity: sha512-EvYTiXet5XqweYGClEmpu3BoxmsQ4hkj3QaYA6qEnigCWffTP3vNRwBReTdrwDwo7OoJ3wM8Uoe9Uk4n+d4hfg==}
@@ -7518,8 +7519,8 @@ packages:
resolution: {integrity: sha512-kUiFyUQDiVdpeeL/qwOZAdDZrYFTwqppwNZDxOXcPSKeqGCZe8ajbbN64OAS7VlGMnXWxEEHcjhyFzB9gtHn5w==}
hasBin: true
- '@theguild/components@9.3.4':
- resolution: {integrity: sha512-V0Q70chn0m7VimoAA8KDl5ghrcKjoJqzOTjU8C3k/sNbLJPa1rBSbpWyubWlqTlVGjCtmNsFaRDDbccETlZL8w==}
+ '@theguild/components@9.5.0':
+ resolution: {integrity: sha512-NNvaxymlwyX5SQKGSUMDeIZ0nfqoDgwak39sL79wiuEzUmwRU6WAe+QpSDPIC3529Iuf5lAMqi5x6UoPn5yStA==}
peerDependencies:
'@theguild/tailwind-config': ^0.6.2
next: ^13 || ^14 || ^15.0.0
@@ -23985,7 +23986,7 @@ snapshots:
typescript: 4.9.5
yargs: 16.2.0
- '@theguild/components@9.3.4(@theguild/tailwind-config@0.6.3(postcss-import@16.1.0(postcss@8.4.49))(postcss-lightningcss@1.0.1(postcss@8.4.49))(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.3))))(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(immer@10.1.1)(next@15.2.4(@babel/core@7.22.9)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)(use-sync-external-store@1.2.0(react@19.0.0))':
+ '@theguild/components@9.5.0(@theguild/tailwind-config@0.6.3(postcss-import@16.1.0(postcss@8.4.49))(postcss-lightningcss@1.0.1(postcss@8.4.49))(tailwindcss@3.4.17(ts-node@10.9.2(@swc/core@1.10.6(@swc/helpers@0.5.15))(@types/node@22.10.5)(typescript@5.7.3))))(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(immer@10.1.1)(next@15.2.4(@babel/core@7.22.9)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3)(use-sync-external-store@1.2.0(react@19.0.0))':
dependencies:
'@giscus/react': 3.1.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@next/bundle-analyzer': 15.1.5