- {sectionTo ? (
+ {items ? (
+
+ ) : sectionTo ? (
diff --git a/src/utils/blog-post-seo.ts b/src/utils/blog-post-seo.ts
index caf61b872..405935d3f 100644
--- a/src/utils/blog-post-seo.ts
+++ b/src/utils/blog-post-seo.ts
@@ -37,6 +37,13 @@ export function getBlogSocialImageUrl(headerImage: string | undefined) {
})
}
+export function getBlogBreadcrumbItems(post: { title: string; slug: string }) {
+ return [
+ { label: 'Blog', to: '/blog' },
+ { label: post.title, to: `/blog/${post.slug}` },
+ ]
+}
+
export function getBlogPostingJsonLd(post: BlogPostHeadData) {
const pageUrl = canonicalUrl(`/blog/${post.slug}`)
@@ -44,6 +51,16 @@ export function getBlogPostingJsonLd(post: BlogPostHeadData) {
'@context': 'https://schema.org',
'@graph': [
getTanStackOrganizationJsonLd(),
+ {
+ '@type': 'BreadcrumbList',
+ '@id': `${pageUrl}#breadcrumb`,
+ itemListElement: getBlogBreadcrumbItems(post).map((item, index) => ({
+ '@type': 'ListItem',
+ position: index + 1,
+ name: item.label,
+ item: canonicalUrl(item.to),
+ })),
+ },
{
'@type': 'BlogPosting' as const,
'@id': `${pageUrl}#blog-post`,
diff --git a/tests/blog-post-seo.test.ts b/tests/blog-post-seo.test.ts
index 9fe2d9b0e..44dcb3680 100644
--- a/tests/blog-post-seo.test.ts
+++ b/tests/blog-post-seo.test.ts
@@ -8,6 +8,7 @@ import {
} from '../src/utils/blog-format'
import {
type BlogPostHeadData,
+ getBlogBreadcrumbItems,
getBlogPostHead,
getBlogSocialImageUrl,
} from '../src/utils/blog-post-seo'
@@ -235,3 +236,36 @@ test('known modification dates reach frontmatter, JSON-LD, and article metadata'
true,
)
})
+
+test('blog breadcrumbs use the visible trail with canonical ordered URLs', () => {
+ const post = {
+ ...basePost,
+ slug: '2026/typed-routing',
+ title: 'Routing &
',
+ }
+ const trail = getBlogBreadcrumbItems(post)
+ assert.deepEqual(trail, [
+ { label: 'Blog', to: '/blog' },
+ { label: post.title, to: '/blog/2026/typed-routing' },
+ ])
+ const graph = getJsonLd(getBlogPostHead(post))['@graph']
+ const breadcrumb = graph.find((node) => node['@type'] === 'BreadcrumbList')
+ assert.deepEqual(breadcrumb, {
+ '@type': 'BreadcrumbList',
+ '@id': 'https://tanstack.com/blog/2026/typed-routing#breadcrumb',
+ itemListElement: [
+ {
+ '@type': 'ListItem',
+ position: 1,
+ name: 'Blog',
+ item: 'https://tanstack.com/blog',
+ },
+ {
+ '@type': 'ListItem',
+ position: 2,
+ name: post.title,
+ item: 'https://tanstack.com/blog/2026/typed-routing',
+ },
+ ],
+ })
+})