Skip to content

feat(webapp): add JSON-LD to tag and source detail pages#5684

Merged
nimrodkra merged 3 commits intomainfrom
seo/pr2-json-ld
Mar 9, 2026
Merged

feat(webapp): add JSON-LD to tag and source detail pages#5684
nimrodkra merged 3 commits intomainfrom
seo/pr2-json-ld

Conversation

@nimrodkra
Copy link
Member

@nimrodkra nimrodkra commented Mar 9, 2026

Summary

  • add schema.org JSON-LD to tag detail pages with CollectionPage, ItemList, and BreadcrumbList
  • add schema.org JSON-LD to source detail pages with Organization, CollectionPage, and BreadcrumbList
  • render JSON-LD in page <head> using existing squad-page pattern

Why this matters

Structured data helps Google understand page intent and entity relationships, which can improve eligibility for rich-result presentation. Adding this to tag/source detail pages closes a major discoverability gap versus our already-optimized index and squad surfaces.

Test plan

  • pnpm --filter webapp lint
  • pnpm --filter webapp build (fails locally due DNS lookup for api.local.fylla.dev during prerender in this environment)

Made with Cursor

Preview domain

https://seo-pr2-json-ld.preview.app.daily.dev

Render schema.org JSON-LD in tag and source detail pages using CollectionPage, BreadcrumbList, and supporting entities so crawlers can better interpret these surfaces.

Made-with: Cursor
@vercel
Copy link

vercel bot commented Mar 9, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
daily-webapp Ready Ready Preview Mar 9, 2026 2:09pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
storybook Ignored Ignored Mar 9, 2026 2:09pm

Request Review

@nimrodkra
Copy link
Member Author

@claude please review this pr

@claude
Copy link
Contributor

claude bot commented Mar 9, 2026

Claude finished @nimrodkra's task in 1m 54s —— View job


PR Review

  • Read changed files
  • Analyze JSON-LD implementation
  • Check for issues and best practices
  • Post review feedback

Overall this is a clean, well-structured addition that follows the existing squad-page JSON-LD pattern closely. A few things worth addressing:


Issues

1. logo value should be an ImageObject, not a plain URL string

packages/webapp/pages/sources/[source].tsx line 144:

...(source.image && { logo: source.image }),

Google's structured data guidelines require logo on Organization to be an ImageObject:

...(source.image && {
  logo: { '@type': 'ImageObject', url: source.image },
}),

Note: the squad page (squads/[handle]/index.tsx line 95) has the same issue — logo: squad.image — so this is a pre-existing inconsistency, but it's still worth fixing here to avoid validation warnings in Google's Rich Results Test.

Fix this →


2. ItemList is always emitted even when topPosts is empty

packages/webapp/pages/tags/[tag].tsx lines 195–205:

{
  '@type': 'ItemList',
  '@id': `${tagUrl}#items`,
  numberOfItems: topPosts.length,   // could be 0
  itemListElement: topPosts.map(), // could be []
}

An ItemList with 0 items is technically valid but provides no signal to crawlers. Consider skipping this block when topPosts.length === 0:

...(topPosts.length > 0 && {
  '@type': 'ItemList',
  '@id': `${tagUrl}#items`,
  numberOfItems: topPosts.length,
  itemListElement: topPosts.map(),
}),

Or simply spread it conditionally into the @graph array.

Fix this →


3. CollectionPage on source page is missing description

packages/webapp/pages/sources/[source].tsx lines 148–154: the CollectionPage node has no description, while the tag page's CollectionPage includes one. The source data may already have a description — it's available in the Organization node but could be mirrored to CollectionPage for consistency:

{
  '@type': 'CollectionPage',
  '@id': `${sourcePageUrl}#page`,
  url: sourcePageUrl,
  name: `${source.name} posts on daily.dev`,
  ...(source.description && { description: source.description }),
  about: { '@id': `${sourcePageUrl}#organization` },
  isPartOf: { '@type': 'WebSite', url: appOrigin },
},

Nits

  • getSourcePageJsonLd(source) (line 220) is called on every render without memoization. Since this is a statically generated page the impact is negligible, but wrapping it in useMemo would be consistent with how other expensive derivations are handled in these files.
  • The tag page's getTagPageJsonLd is called inline (line 278–280) without useMemo for the same reason — also low impact, just noting the inconsistency.

What looks good

  • Pattern is consistent with the existing squad-page JSON-LD implementation.
  • encodeURIComponent used correctly for dynamic URL segments.
  • JSON-LD is injected into <Head> via dangerouslySetInnerHTML — correct approach for application/ld+json.
  • Tag page correctly guards getTagPageJsonLd behind if (initialData) so null data never reaches the serializer.
  • topPosts in getStaticProps already filters out posts without titles, so name: post.title || '' fallback is safe.
  • Both /tags and /sources index pages exist, so the intermediate breadcrumb item URLs are valid.

Use an ImageObject for source organization logos, include source page descriptions on CollectionPage, and emit tag ItemList schema only when top posts are available.

Made-with: Cursor
@nimrodkra nimrodkra enabled auto-merge (squash) March 9, 2026 14:06
@nimrodkra nimrodkra merged commit b2fe727 into main Mar 9, 2026
11 checks passed
@nimrodkra nimrodkra deleted the seo/pr2-json-ld branch March 9, 2026 14:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants