Feat SEO Package (Overview, Heading Structure, Links & JSON LD Preview)#416
Feat SEO Package (Overview, Heading Structure, Links & JSON LD Preview)#416abedshaaban wants to merge 62 commits intoTanStack:mainfrom
Conversation
…functionality This commit introduces a new README.md file for the SEO tab in the devtools package. It outlines the purpose of the SEO tab, including its major features such as Social Previews, SERP Previews, JSON-LD Previews, and more. Each section provides an overview of functionality, data sources, and how the previews are rendered, enhancing the documentation for better user understanding.
…structure, and links preview This commit introduces several new sections to the SEO tab in the devtools package, enhancing its functionality. The new features include: - **JSON-LD Preview**: Parses and validates JSON-LD scripts on the page, providing detailed feedback on required and recommended attributes. - **Heading Structure Preview**: Analyzes heading tags (`h1` to `h6`) for hierarchy and common issues, ensuring proper SEO practices. - **Links Preview**: Scans all links on the page, classifying them as internal, external, or invalid, and reports on accessibility and SEO-related issues. Additionally, the SEO tab navigation has been updated to include these new sections, improving user experience and accessibility of SEO insights.
This commit refactors the SEO tab components to standardize the handling of severity levels for issues. The `Severity` type has been replaced with `SeoSeverity`, and the `severityColor` function has been removed in favor of a centralized `seoSeverityColor` function. This change improves code consistency and maintainability across the `canonical-url-preview`, `heading-structure-preview`, `json-ld-preview`, and `links-preview` components, ensuring a unified approach to displaying issue severity in the SEO analysis features.
This commit adds a canonical link and robots meta tag to the basic example's HTML file, improving SEO capabilities. Additionally, it refactors the SEO tab components to utilize the `Show` component for conditional rendering of issues, enhancing the user experience by only displaying relevant information when applicable. This change streamlines the presentation of SEO analysis results across the canonical URL, heading structure, and links preview sections.
…lysis This commit adds a new SEO overview section to the devtools package, aggregating insights from various SEO components including canonical URLs, social previews, SERP previews, JSON-LD, heading structure, and links. It implements a health scoring system to provide a quick assessment of SEO status, highlighting issues and offering hints for improvement. Additionally, it refactors existing components to enhance data handling and presentation, improving the overall user experience in the SEO tab.
…reporting This commit introduces new styles for the SEO tab components, improving the visual presentation of SEO analysis results. It adds structured issue reporting for SEO elements, including headings, JSON-LD, and links, utilizing a consistent design for severity indicators. Additionally, it refactors existing components to enhance readability and maintainability, ensuring a cohesive user experience across the SEO tab.
This commit introduces new styles for the SEO tab components, including enhanced visual presentation for SEO analysis results. It refactors the handling of severity indicators across various sections, such as headings, JSON-LD, and links, utilizing a consistent design approach. Additionally, it improves the structure and readability of the code, ensuring a cohesive user experience throughout the SEO tab.
…ization This commit enhances the SEO tab by updating styles for the health score indicators, including a new design for the health track and fill elements. It refactors the health score rendering logic to utilize a more consistent approach across components, improving accessibility with ARIA attributes. Additionally, it introduces a sorting function for links in the report, ensuring a clearer display order based on link types. These changes aim to provide a more cohesive and visually appealing user experience in the SEO analysis features.
This commit enhances the LinksPreviewSection by introducing an accordion-style layout for displaying links, allowing users to expand and collapse groups of links categorized by type (internal, external, non-web, invalid). It adds new styles for the accordion components, improving the visual organization of link reports. Additionally, it refactors the existing link rendering logic to accommodate the new structure, enhancing user experience and accessibility in the SEO analysis features.
…on features This commit introduces new styles for the JSON-LD preview component, improving the visual presentation of structured data. It adds functionality for validating supported schema types and enhances the display of entity previews, including detailed rows for required and recommended fields. Additionally, it refactors the health scoring system to account for missing schema attributes, providing clearer insights into SEO performance. These changes aim to improve user experience and accessibility in the SEO tab.
…tures This commit introduces a comprehensive update to the SEO overview section, adding a scoring system for subsections based on issue severity. It includes new styles for the score ring visualization, improving the presentation of SEO health metrics. Additionally, it refactors the issue reporting logic to provide clearer insights into the status of SEO elements, enhancing user experience and accessibility in the SEO tab.
…links preview in SEO tab This commit enhances the SEO tab by introducing new navigation buttons for 'Heading Structure' and 'Links Preview', allowing users to easily switch between these views. It also updates the display logic to show the corresponding sections when selected, improving the overall user experience and accessibility of SEO insights. The SEO overview section has been adjusted to maintain a cohesive structure.
…and scrollbar customization This commit updates the styles for the seoSubNav component, adding responsive design features for smaller screens, including horizontal scrolling and custom scrollbar styles. It also ensures that the seoSubNavLabel maintains proper layout with flex properties, enhancing the overall user experience in the SEO tab.
…inks preview functionality This commit modifies the package.json to improve testing scripts by adding a command to clear the NX daemon and updating the size limit for the devtools package. Additionally, it refactors the JSON-LD and links preview components to enhance readability and maintainability, including changes to function declarations and formatting for better code clarity. These updates aim to improve the overall user experience and accessibility in the SEO tab.
… tab components This commit refactors the SEO tab components by cleaning up imports related to severity handling and ensuring consistent text handling by removing unnecessary nullish coalescing and optional chaining. These changes enhance code readability and maintainability across the heading structure, JSON-LD, and links preview components.
…ew component This commit refactors the classifyLink function in the links preview component by removing unnecessary checks for non-web links and the 'nofollow' issue reporting. It enhances the handling of relative paths and same-document fragments to align with browser behavior, improving code clarity and maintainability in the SEO tab.
…README This commit removes the unused 'seoOverviewFootnote' style and its corresponding JSX element from the SEO overview section. Additionally, it updates the README to streamline the description of checks included in the SEO tab, enhancing clarity and conciseness. These changes improve code maintainability and documentation accuracy.
This commit modifies the size limit for the devtools package in package.json, increasing the limit from 60 KB to 69 KB. This change reflects adjustments in the package's size requirements, ensuring accurate size tracking for future development.
… in SEO tab components This commit updates the SEO tab components by standardizing the capitalization of section titles and improving code formatting for better readability. Changes include updating button labels to 'SEO Overview' and 'Social Previews', as well as enhancing the structure of JSX elements for consistency. These adjustments aim to enhance the overall clarity and maintainability of the code.
This commit modifies the titles of the 'Links' and 'JSON-LD' sections in the SEO overview to 'Links Preview' and 'JSON-LD Preview', respectively. These changes aim to enhance clarity and consistency in the presentation of SEO insights, aligning with previous updates to standardize capitalization and improve formatting across the SEO tab components.
…ed data analysis This commit adds a new SEO tab in the devtools, featuring live head-driven social and SERP previews, structured data (JSON-LD) analysis, heading and link assessments, and an overview that scores and links to each section. This enhancement aims to provide users with comprehensive SEO insights and improve the overall functionality of the devtools.
…nonicalPageData This commit modifies the export statements for the CanonicalPageIssue and CanonicalPageData types in the SEO tab components, changing them from 'export type' to 'type'. This adjustment aims to streamline the code structure and improve consistency in type declarations across the module.
…link and improving robots handling This commit removes the canonical link from the basic example HTML file and updates the robots handling logic in the canonical URL data module. The changes include refining the conditions for indexability and follow directives, ensuring more accurate SEO assessments. Additionally, the links preview component is updated to enforce the inclusion of both 'noopener' and 'noreferrer' for external links with target='_blank'. These adjustments aim to improve the overall functionality and security of the SEO tab.
…tion This commit introduces a new hook, useLocationChanges, that allows components to react to changes in the browser's location. The hook sets up listeners for pushState, replaceState, and popstate events, enabling efficient updates when the URL changes. Additionally, it integrates with the SEO tab components to enhance responsiveness to location changes, improving user experience and functionality.
This commit refactors the links-preview component by consolidating import statements for better clarity and organization. The countBySeverity function and SeoSectionSummary type are now imported separately, enhancing code readability and maintainability.
There was a problem hiding this comment.
Actionable comments posted: 7
♻️ Duplicate comments (3)
packages/devtools-seo/src/json-ld-preview.tsx (2)
457-471:⚠️ Potential issue | 🟠 MajorUse the shared section scorer here too.
This card uses 20/10/2 penalties, while
packages/devtools-seo/src/seo-section-summary.tsuses 22/9/2 insectionHealthScore(). The same JSON-LD issues can therefore show one percentage here and a different one in the overview ring.♻️ Proposed fix
+import { sectionHealthScore } from './seo-section-summary' import type { SeoSectionSummary } from './seo-section-summary' @@ function getJsonLdScore(entries: Array<JsonLdEntry>): number { - let errors = 0 - let warnings = 0 - let infos = 0 - - for (const entry of entries) { - for (const issue of entry.issues) { - if (issue.severity === 'error') errors += 1 - else if (issue.severity === 'warning') warnings += 1 - else infos += 1 - } - } - - const penalty = Math.min(100, errors * 20 + warnings * 10 + infos * 2) - return Math.max(0, 100 - penalty) + return sectionHealthScore(entries.flatMap((entry) => entry.issues)) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/devtools-seo/src/json-ld-preview.tsx` around lines 457 - 471, getJsonLdScore duplicates scoring logic (20/10/2) that differs from the canonical scorer used in sectionHealthScore() (22/9/2); replace the bespoke logic in getJsonLdScore with a call to the shared section scorer used by seo-section-summary.ts (or extract the shared scoring function and import it) so both views compute the same percentage for the same JsonLdEntry issues; locate getJsonLdScore and either call sectionHealthScore (or the newly exported shared scorer) with the entry issues or refactor sectionHealthScore into a shared helper and use that helper in getJsonLdScore.
205-217:⚠️ Potential issue | 🟠 MajorDon't penalize keys outside this curated ruleset.
SUPPORTED_RULESis a subset, not an exhaustive schema allowlist. With this block, any extra top-level property becomes a warning and lowers the score, so valid markup looks unhealthy just because this tool doesn't know every schema field yet.♻️ Proposed fix
- const allowedSet = new Set([ - ...rules.required, - ...rules.recommended, - ...rules.optional, - ...RESERVED_KEYS, - ]) - const unknownKeys = Object.keys(entity).filter((key) => !allowedSet.has(key)) - if (unknownKeys.length > 0) { - issues.push({ - severity: 'warning', - message: `Possible invalid attributes for ${typeName}: ${unknownKeys.join(', ')}`, - }) - }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/devtools-seo/src/json-ld-preview.tsx` around lines 205 - 217, The current code constructs allowedSet and pushes a warning for any unknownKeys (derived from Object.keys(entity)), which penalizes valid but uncaptured schema fields; update the logic in json-ld-preview.tsx to stop treating unknown top-level properties as issues by removing or disabling the push to issues for unknownKeys (i.e., delete or guard the block that adds the warning), or alternatively narrow the check to only flag truly reserved/misplaced keys (use RESERVED_KEYS directly rather than allowedSet); refer to allowedSet, unknownKeys, entity and issues when locating the code to change.packages/devtools-seo/src/links-preview.tsx (1)
28-39:⚠️ Potential issue | 🟠 MajorDon't report an “accessible label” error from a text-only heuristic.
This still only checks
textContent,aria-label, andtitle, so links named viaaria-labelledbyor descendantimg[alt]will be reported as errors even though they have a valid accessible name. Either broaden the name lookup or narrow the message to the signals you actually inspect.♻️ Safe immediate fix
if (!text) { issues.push({ severity: 'error', - message: 'Missing link text or accessible label.', + message: 'Missing link text, aria-label, or title.', }) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/devtools-seo/src/links-preview.tsx` around lines 28 - 39, The current heuristic computes text from the local variables named text (using anchor.textContent, anchor.getAttribute('aria-label'), and anchor.getAttribute('title')) but then pushes a generic error about an "accessible label" which is misleading; update the issues.push message (and/or the error severity text) to only refer to the signals actually checked (e.g., "Missing link text, aria-label, or title") or alternatively expand the name lookup to include aria-labelledby and descendant img[alt] before deciding to push an error; specifically, modify the code around the text variable and the issues.push call in links-preview.tsx (referencing the text variable, anchor element, and LinkIssue type) so the message accurately reflects the checked attributes or add logic to resolve aria-labelledby and img[alt] into text first.
🧹 Nitpick comments (6)
packages/devtools-seo/src/tokens.ts (2)
208-208: Remove duplicate fallback infontFamily.sans
fontFamily.sansrepeatssans-seriftwice; keeping it once is cleaner.Suggested cleanup
- sans: 'ui-sans-serif, Inter, system-ui, sans-serif, sans-serif', + sans: 'ui-sans-serif, Inter, system-ui, sans-serif',🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/devtools-seo/src/tokens.ts` at line 208, fontFamily.sans currently lists the fallback "sans-serif" twice; update the definition (the fontFamily.sans entry) to remove the duplicated "sans-serif" so the value reads something like "ui-sans-serif, Inter, system-ui, sans-serif" with only one "sans-serif".
8-19: Deduplicateneutralandgrayscales to avoid driftThese two palettes are identical copies right now. Consider sharing one source object so future edits can’t diverge unintentionally.
Also applies to: 32-43
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/devtools-seo/src/tokens.ts` around lines 8 - 19, The neutral and gray color scales in tokens.ts are duplicated and risk drifting; replace the duplicate by creating a single shared object (e.g., a single palette constant or export) and reference it from both places instead of repeating values so both neutral and gray (or whichever export names are required) point to the same source; update references to use the shared symbol (neutral/gray) and remove the redundant literal blocks to ensure future edits stay in sync.packages/devtools-seo/src/use-seo-styles.ts (1)
15-28: Duplicate CSS property inseoTabContainer.
overflow-y: auto;is declared twice (lines 21 and 27). The second declaration is redundant.🧹 Remove duplicate property
seoTabContainer: css` padding: 0; margin: 0 auto; background: ${t(colors.white, colors.darkGray[700])}; border-radius: 8px; box-shadow: none; overflow-y: auto; height: 100%; display: flex; flex-direction: column; gap: 0; width: 100%; - overflow-y: auto; `,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/devtools-seo/src/use-seo-styles.ts` around lines 15 - 28, The css block for seoTabContainer contains a duplicate declaration of overflow-y: auto; — remove the redundant occurrence so seoTabContainer only declares overflow-y: auto once (keep the first or the most appropriate placement) within the css template literal to avoid duplication; update the css for seoTabContainer accordingly.examples/react/seo/src/index.tsx (1)
7-12: Consider wrapping withStrictModefor consistency.Other React examples in this repo (e.g.,
a11y-devtools,custom-devtools) wrap the render content withStrictMode. This helps catch potential issues during development.♻️ Add StrictMode wrapper
+import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import { seoDevtoolsPlugin } from '@tanstack/devtools-seo/react' import { TanStackDevtools } from '@tanstack/react-devtools' import App from './App' createRoot(document.getElementById('root')!).render( - <> + <StrictMode> <App /> <TanStackDevtools plugins={[seoDevtoolsPlugin()]} /> - </>, + </StrictMode>, )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/react/seo/src/index.tsx` around lines 7 - 12, The render call currently mounts <> <App /> <TanStackDevtools plugins={[seoDevtoolsPlugin()]} /> </> without React StrictMode; wrap the rendered JSX with StrictMode to match other examples. Update the code that calls createRoot(...).render(...) to render <StrictMode><App /><TanStackDevtools plugins={[seoDevtoolsPlugin()]} /></StrictMode>, and ensure StrictMode is imported (or use React.StrictMode) alongside createRoot; keep the same components (App, TanStackDevtools, seoDevtoolsPlugin) and plugin usage.packages/devtools-seo/src/social-previews.tsx (1)
259-267: Avoid index-coupling betweenreports()andSOCIALS.Using
SOCIALS[i()]with non-null assertions is brittle; includenetwork+accentdirectly in each report and render from report data only.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/devtools-seo/src/social-previews.tsx` around lines 259 - 267, The JSX currently couples reports() to the separate SOCIALS array by using SOCIALS[i()] and non-null assertions; instead add the network and accent properties into each report object (e.g., include report.network and report.accent when building reports()) and update the For renderer to pass those directly to <SocialPreview> (use meta={report.found} network={report.network} accent={report.accent}), removing the index lookup and the non-null assertions so rendering relies only on report data.packages/devtools-seo/src/solid-panel.tsx (1)
6-8: Import and useTanStackDevtoolsThemeinstead of a local theme union.This keeps the type definition in sync with
@tanstack/devtools-uiand ensures automatic alignment if supported themes expand in the future.♻️ Proposed typing change
import { ThemeContextProvider } from '@tanstack/devtools-ui' +import type { TanStackDevtoolsTheme } from '@tanstack/devtools-ui' import { SeoTab } from './seo-tab' type SeoPluginPanelProps = { - theme: 'light' | 'dark' + theme: TanStackDevtoolsTheme devtoolsOpen: boolean }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/devtools-seo/src/solid-panel.tsx` around lines 6 - 8, Replace the local union type for the theme prop with the exported TanStackDevtoolsTheme type from `@tanstack/devtools-ui`: import TanStackDevtoolsTheme and update the SeoPluginPanelProps type (and any references to the theme prop in the Solid component) to use TanStackDevtoolsTheme instead of 'light' | 'dark' so the prop's allowed values stay in sync with the devtools UI package.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/devtools-seo/src/heading-structure-preview.tsx`:
- Around line 175-177: The SectionDescription text is outdated: it says "This
section scans once when opened" but the component now rescans on location
changes (useLocationChanges()). Update the displayed description string in
heading-structure-preview.tsx (inside the SectionDescription component) to
reflect current behavior—e.g., mention that the panel rescans on location
changes or whenever the location updates—so the copy accurately matches the
scanning behavior implemented by useLocationChanges().
In `@packages/devtools-seo/src/json-ld-preview.tsx`:
- Around line 135-157: The validator currently rejects any non-string `@context`;
update validateContext to accept JSON‑LD permissible types: explicitly allow
null (context === null) and object (typeof context === 'object' && context !==
null) and handle arrays by verifying at least one element is a valid entry (a
schema.org string present in VALID_SCHEMA_CONTEXTS, an object, or null). Keep
the existing string branch for direct IRIs using VALID_SCHEMA_CONTEXTS, return
[] for accepted cases, and only return the error messages (invalid type or
invalid array contents) when none of these allowed forms are met; reference
validateContext, JsonLdValue, and VALID_SCHEMA_CONTEXTS to locate the changes.
In `@packages/devtools-seo/src/seo-tab.tsx`:
- Around line 20-63: The nav-based subview switcher should expose explicit tab
semantics: add role="tablist" to the <nav class={styles().seoSubNav}> and for
each button (the ones using class `${styles().seoSubNavLabel}` and
activeView()/setActiveView()) add role="tab" and set aria-selected={activeView()
=== '<view-name>'} (true for the active view, false otherwise); keep the
existing onClick handlers calling setActiveView(...) and ensure the active
styling logic remains tied to activeView() so assistive tech and keyboard users
can identify the selected tab.
In `@packages/devtools-seo/src/serp-preview.tsx`:
- Around line 525-530: SerpPreviewSection only updates on head mutations; also
subscribe to client-side navigation events so setSerp(getSerpFromHead()) runs on
route changes: in SerpPreviewSection add a listener for 'popstate' and a small
wrapper that patches history.pushState/history.replaceState to dispatch a custom
'locationchange' event, then listen for that event and call
setSerp(getSerpFromHead()); ensure you register these listeners alongside
useHeadChanges and remove/restore the listeners/patch on cleanup to avoid leaks.
- Around line 327-338: The title overflow is only computed for desktop
(titleOverflow) so mobile previews can be truncated without detection; add a
separate mobile title overflow check (e.g., titleOverflowMobile) and compute it
against the mobile constraints (use measureTextWidth or wrapTextByWidth with
MOBILE_TITLE_WIDTH_PX, TITLE_FONT and MOBILE_TITLE_MAX_LINES or
MOBILE_TITLE_WIDTH_PX) similar to how descriptionOverflowMobile is computed,
then include that property where the overflow object is constructed so mobile
title truncation is reported independently from desktop.
In `@packages/devtools-seo/src/social-previews.tsx`:
- Around line 34-39: Replace the unsupported twitter:url tag with og:url in the
tags definitions: update the tag object where key === 'twitter:url' to use key
'og:url' (keep prop 'url') and make the same replacement in the second
occurrence later in the file (the other tags array around lines 146-158); ensure
both tags arrays in social-previews.tsx are changed so Twitter/X cards use
og:url for the canonical URL.
In `@packages/devtools-seo/src/tokens.ts`:
- Around line 274-275: The shadow.xs function currently ignores its color
parameter and returns a hardcoded color; update the xs arrow function in
tokens.ts (the shadow.xs definition) to use the provided color parameter in the
returned template string (e.g., interpolate the color variable instead of the
fixed 'rgb(0 0 0 / 0.05)') while preserving the existing return type (as const)
and default value for the parameter.
---
Duplicate comments:
In `@packages/devtools-seo/src/json-ld-preview.tsx`:
- Around line 457-471: getJsonLdScore duplicates scoring logic (20/10/2) that
differs from the canonical scorer used in sectionHealthScore() (22/9/2); replace
the bespoke logic in getJsonLdScore with a call to the shared section scorer
used by seo-section-summary.ts (or extract the shared scoring function and
import it) so both views compute the same percentage for the same JsonLdEntry
issues; locate getJsonLdScore and either call sectionHealthScore (or the newly
exported shared scorer) with the entry issues or refactor sectionHealthScore
into a shared helper and use that helper in getJsonLdScore.
- Around line 205-217: The current code constructs allowedSet and pushes a
warning for any unknownKeys (derived from Object.keys(entity)), which penalizes
valid but uncaptured schema fields; update the logic in json-ld-preview.tsx to
stop treating unknown top-level properties as issues by removing or disabling
the push to issues for unknownKeys (i.e., delete or guard the block that adds
the warning), or alternatively narrow the check to only flag truly
reserved/misplaced keys (use RESERVED_KEYS directly rather than allowedSet);
refer to allowedSet, unknownKeys, entity and issues when locating the code to
change.
In `@packages/devtools-seo/src/links-preview.tsx`:
- Around line 28-39: The current heuristic computes text from the local
variables named text (using anchor.textContent,
anchor.getAttribute('aria-label'), and anchor.getAttribute('title')) but then
pushes a generic error about an "accessible label" which is misleading; update
the issues.push message (and/or the error severity text) to only refer to the
signals actually checked (e.g., "Missing link text, aria-label, or title") or
alternatively expand the name lookup to include aria-labelledby and descendant
img[alt] before deciding to push an error; specifically, modify the code around
the text variable and the issues.push call in links-preview.tsx (referencing the
text variable, anchor element, and LinkIssue type) so the message accurately
reflects the checked attributes or add logic to resolve aria-labelledby and
img[alt] into text first.
---
Nitpick comments:
In `@examples/react/seo/src/index.tsx`:
- Around line 7-12: The render call currently mounts <> <App />
<TanStackDevtools plugins={[seoDevtoolsPlugin()]} /> </> without React
StrictMode; wrap the rendered JSX with StrictMode to match other examples.
Update the code that calls createRoot(...).render(...) to render
<StrictMode><App /><TanStackDevtools plugins={[seoDevtoolsPlugin()]}
/></StrictMode>, and ensure StrictMode is imported (or use React.StrictMode)
alongside createRoot; keep the same components (App, TanStackDevtools,
seoDevtoolsPlugin) and plugin usage.
In `@packages/devtools-seo/src/social-previews.tsx`:
- Around line 259-267: The JSX currently couples reports() to the separate
SOCIALS array by using SOCIALS[i()] and non-null assertions; instead add the
network and accent properties into each report object (e.g., include
report.network and report.accent when building reports()) and update the For
renderer to pass those directly to <SocialPreview> (use meta={report.found}
network={report.network} accent={report.accent}), removing the index lookup and
the non-null assertions so rendering relies only on report data.
In `@packages/devtools-seo/src/solid-panel.tsx`:
- Around line 6-8: Replace the local union type for the theme prop with the
exported TanStackDevtoolsTheme type from `@tanstack/devtools-ui`: import
TanStackDevtoolsTheme and update the SeoPluginPanelProps type (and any
references to the theme prop in the Solid component) to use
TanStackDevtoolsTheme instead of 'light' | 'dark' so the prop's allowed values
stay in sync with the devtools UI package.
In `@packages/devtools-seo/src/tokens.ts`:
- Line 208: fontFamily.sans currently lists the fallback "sans-serif" twice;
update the definition (the fontFamily.sans entry) to remove the duplicated
"sans-serif" so the value reads something like "ui-sans-serif, Inter, system-ui,
sans-serif" with only one "sans-serif".
- Around line 8-19: The neutral and gray color scales in tokens.ts are
duplicated and risk drifting; replace the duplicate by creating a single shared
object (e.g., a single palette constant or export) and reference it from both
places instead of repeating values so both neutral and gray (or whichever export
names are required) point to the same source; update references to use the
shared symbol (neutral/gray) and remove the redundant literal blocks to ensure
future edits stay in sync.
In `@packages/devtools-seo/src/use-seo-styles.ts`:
- Around line 15-28: The css block for seoTabContainer contains a duplicate
declaration of overflow-y: auto; — remove the redundant occurrence so
seoTabContainer only declares overflow-y: auto once (keep the first or the most
appropriate placement) within the css template literal to avoid duplication;
update the css for seoTabContainer accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0f6bc8bd-4a95-4895-b832-efbe64854c54
⛔ Files ignored due to path filters (2)
examples/react/seo/public/emblem-light.svgis excluded by!**/*.svgpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (35)
.changeset/puny-games-bow.mdexamples/react/basic/src/setup.tsxexamples/react/seo/index.htmlexamples/react/seo/package.jsonexamples/react/seo/src/App.tsxexamples/react/seo/src/index.tsxexamples/react/seo/tsconfig.jsonexamples/react/seo/vite.config.tspackage.jsonpackages/devtools-seo/package.jsonpackages/devtools-seo/src/canonical-url-data.tspackages/devtools-seo/src/core.tsxpackages/devtools-seo/src/devtools-dom-filter.tspackages/devtools-seo/src/heading-structure-preview.tsxpackages/devtools-seo/src/hooks/use-head-changes.tspackages/devtools-seo/src/hooks/use-location-changes.tspackages/devtools-seo/src/index.tspackages/devtools-seo/src/json-ld-preview.tsxpackages/devtools-seo/src/links-preview.tsxpackages/devtools-seo/src/react/SeoDevtools.tsxpackages/devtools-seo/src/react/index.tspackages/devtools-seo/src/react/plugin.tspackages/devtools-seo/src/seo-overview.tsxpackages/devtools-seo/src/seo-section-summary.tspackages/devtools-seo/src/seo-severity.tspackages/devtools-seo/src/seo-tab.tsxpackages/devtools-seo/src/serp-preview.tsxpackages/devtools-seo/src/social-previews.tsxpackages/devtools-seo/src/solid-panel.tsxpackages/devtools-seo/src/tokens.tspackages/devtools-seo/src/use-seo-styles.tspackages/devtools-seo/tsconfig.jsonpackages/devtools-seo/vite.config.tspackages/devtools/src/styles/use-styles.tspackages/devtools/src/tabs/index.tsx
💤 Files with no reviewable changes (1)
- packages/devtools/src/styles/use-styles.ts
✅ Files skipped from review due to trivial changes (8)
- examples/react/seo/vite.config.ts
- packages/devtools-seo/tsconfig.json
- examples/react/seo/index.html
- examples/react/seo/tsconfig.json
- examples/react/basic/src/setup.tsx
- examples/react/seo/package.json
- packages/devtools-seo/src/index.ts
- packages/devtools-seo/package.json
🚧 Files skipped from review as they are similar to previous changes (1)
- .changeset/puny-games-bow.md
| tags: [ | ||
| { key: 'twitter:title', prop: 'title' }, | ||
| { key: 'twitter:description', prop: 'description' }, | ||
| { key: 'twitter:image', prop: 'image' }, | ||
| { key: 'twitter:url', prop: 'url' }, | ||
| ], |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
According to current official X Developer Cards docs, is twitter:urla supported/required card tag, or should URL come fromog:url / shared URL context?
💡 Result:
twitter:url is not a supported (or required) X Card meta tag in the current official Cards Markup Tag Reference—it’s not listed among the available twitter:* properties. [1]
For the URL associated with the card, X’s docs instead show using Open Graph canonical URL markup (i.e., og:url) alongside the shared URL/card crawl context (the URL included in the post). The “Getting started” example uses og:url (not twitter:url). [2]
So: don’t add twitter:url; use the shared URL itself and (optionally) og:url for the canonical URL. [1][2]
Replace twitter:url with og:url for Twitter/X Card metadata.
twitter:url is not a supported tag in the official X Cards Markup Tag Reference. Use og:url instead for the canonical URL, as shown in X's documentation.
Proposed fix
{
network: 'X/Twitter',
tags: [
{ key: 'twitter:title', prop: 'title' },
{ key: 'twitter:description', prop: 'description' },
{ key: 'twitter:image', prop: 'image' },
- { key: 'twitter:url', prop: 'url' },
+ { key: 'og:url', prop: 'url' },
],
accent: 'twitter',
},Also applies to: lines 146-158
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/devtools-seo/src/social-previews.tsx` around lines 34 - 39, Replace
the unsupported twitter:url tag with og:url in the tags definitions: update the
tag object where key === 'twitter:url' to use key 'og:url' (keep prop 'url') and
make the same replacement in the second occurrence later in the file (the other
tags array around lines 146-158); ensure both tags arrays in social-previews.tsx
are changed so Twitter/X cards use og:url for the canonical URL.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
packages/devtools-seo/src/serp-preview.tsx (1)
27-31:⚠️ Potential issue | 🟠 MajorTrack mobile title overflow separately.
titleOverflowis still computed only against the desktop width, but the mobile card truncates againstMOBILE_TITLE_MAX_WIDTH_PX. Titles that fit under 620px and still overflow 328px will be cut in the mobile preview without any issue being surfaced there or ingetSerpPreviewSummary().♻️ Proposed fix
type SerpOverflow = { - titleOverflow: boolean + titleOverflowDesktop: boolean + titleOverflowMobile: boolean descriptionOverflow: boolean descriptionOverflowMobile: boolean } @@ { message: - 'The title is wider than 600px and it may not be displayed in full length.', - hasIssue: (_, overflow) => overflow.titleOverflow, + 'The title exceeds the desktop preview width and may be trimmed.', + hasIssue: (_, overflow) => overflow.titleOverflowDesktop, }, ] @@ { label: 'Mobile preview', isMobile: true, extraChecks: [ + { + message: 'The title exceeds the mobile preview width and may be trimmed.', + hasIssue: (_, overflow) => overflow.titleOverflowMobile, + }, { message: 'Description exceeds the 3-line limit for mobile view. Please shorten your text to fit within 3 lines.', hasIssue: (_, overflow) => overflow.descriptionOverflowMobile, }, @@ overflow: { - titleOverflow: + titleOverflowDesktop: measureTextWidth(titleText, TITLE_FONT) > DESKTOP_TITLE_MAX_WIDTH_PX, + titleOverflowMobile: + measureTextWidth(titleText, TITLE_FONT) > MOBILE_TITLE_MAX_WIDTH_PX, descriptionOverflow: @@ - if (overflow.titleOverflow) { + if (overflow.titleOverflowDesktop) { issues.push({ severity: 'warning', message: - 'The title is wider than 600px and it may not be displayed in full length.', + 'The title exceeds the desktop preview width and may be trimmed.', + }) + } + if (overflow.titleOverflowMobile) { + issues.push({ + severity: 'warning', + message: 'The title exceeds the mobile preview width and may be trimmed.', }) }Also applies to: 65-69, 87-92, 311-315, 328-330, 407-413
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/devtools-seo/src/serp-preview.tsx` around lines 27 - 31, The SerpOverflow type and downstream logic only track a single titleOverflow computed against the desktop width; add a separate titleOverflowMobile boolean to SerpOverflow, compute it using MOBILE_TITLE_MAX_WIDTH_PX (in the same place where titleOverflow is computed), and update getSerpPreviewSummary() and any places that read SerpOverflow (e.g., preview rendering and summary generation) to consider titleOverflowMobile for mobile card checks so mobile truncation is surfaced correctly. Ensure all spots currently referencing titleOverflow for mobile use are updated to check the new titleOverflowMobile instead of the desktop value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/devtools-seo/src/heading-structure-preview.tsx`:
- Line 42: The object property assignment that sets text currently calls
node.textContent.trim(), which can throw if node.textContent is null; update the
assignment in heading-structure-preview (the code that creates the text property
from node.textContent) to guard against null by supplying an empty-string
fallback or using optional chaining before calling trim so trim is only invoked
on a string, ensuring text is always a safe string (e.g., fall back to '').
In `@packages/devtools-seo/src/serp-preview.tsx`:
- Line 10: The user-facing warning messages hardcode "600px" while the actual
check uses DESKTOP_TITLE_MAX_WIDTH_PX = 620, causing incorrect warnings; update
every hardcoded "600px" message (including the messages near the checks that
reference DESKTOP_TITLE_MAX_WIDTH_PX and similar title-length UI text) to
reference the DESKTOP_TITLE_MAX_WIDTH_PX constant (or interpolate its value at
render time) so the displayed threshold always matches the actual constant used
by the logic (ensure you update all occurrences that mention the desktop title
width).
---
Duplicate comments:
In `@packages/devtools-seo/src/serp-preview.tsx`:
- Around line 27-31: The SerpOverflow type and downstream logic only track a
single titleOverflow computed against the desktop width; add a separate
titleOverflowMobile boolean to SerpOverflow, compute it using
MOBILE_TITLE_MAX_WIDTH_PX (in the same place where titleOverflow is computed),
and update getSerpPreviewSummary() and any places that read SerpOverflow (e.g.,
preview rendering and summary generation) to consider titleOverflowMobile for
mobile card checks so mobile truncation is surfaced correctly. Ensure all spots
currently referencing titleOverflow for mobile use are updated to check the new
titleOverflowMobile instead of the desktop value.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ec78ed10-0f7d-47c3-8dc4-5cede7a6ed04
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (7)
packages/devtools-seo/src/heading-structure-preview.tsxpackages/devtools-seo/src/hooks/use-location-changes.tspackages/devtools-seo/src/json-ld-preview.tsxpackages/devtools-seo/src/seo-tab.tsxpackages/devtools-seo/src/serp-preview.tsxpackages/event-bus-client/package.jsonpackages/event-bus-client/tests/index.test.ts
💤 Files with no reviewable changes (1)
- packages/event-bus-client/package.json
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/devtools-seo/src/seo-tab.tsx
- packages/devtools-seo/src/hooks/use-location-changes.ts
- packages/devtools-seo/src/json-ld-preview.tsx
- Implemented SeoOverviewSection to provide an overview of SEO health and issues. - Created SerpPreviewSection to display how the title and meta description appear in search results. - Developed SocialPreviewsSection to analyze and display social media meta tags for various platforms. - Added navigation for switching between SEO sections in SeoTab. - Introduced utility functions for measuring text width and truncating text for SERP previews.
…nstack-devtools into feat/seo-overview
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
packages/devtools-seo/src/utils/use-seo-styles.ts (1)
15-28: Duplicateoverflow-y: autoproperty.Lines 21 and 27 both declare
overflow-y: auto;. The second declaration is redundant.♻️ Proposed fix
seoTabContainer: css` padding: 0; margin: 0 auto; background: ${t(colors.white, colors.darkGray[700])}; border-radius: 8px; box-shadow: none; - overflow-y: auto; height: 100%; display: flex; flex-direction: column; gap: 0; width: 100%; overflow-y: auto; `,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/devtools-seo/src/utils/use-seo-styles.ts` around lines 15 - 28, The css block for seoTabContainer contains a duplicate property overflow-y: auto; — remove the redundant occurrence so overflow-y: auto is declared only once in the seoTabContainer CSS template (locate the css literal named seoTabContainer in use-seo-styles.ts and delete the second/duplicate overflow-y line).packages/devtools-seo/src/tabs/seo-tab.tsx (1)
20-75: Consider completing the ARIA tablist pattern for better accessibility.The tabs use
role="tab"andaria-selectedcorrectly, but for full ARIA compliance, each tab should have anidandaria-controlspointing to the corresponding panel, and each panel should haverole="tabpanel"andaria-labelledby. This is optional for internal devtools but would improve screen reader navigation.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/devtools-seo/src/tabs/seo-tab.tsx` around lines 20 - 75, The tab buttons in seo-tab.tsx currently have role="tab" and aria-selected but lack id and aria-controls, so add a unique id to each button (e.g., id={`tab-${viewKey}`}) and set aria-controls to the matching panel id (e.g., aria-controls={`panel-${viewKey}`}) where viewKey corresponds to 'overview', 'heading-structure', 'links-preview', 'social-previews', 'serp-preview', 'json-ld-preview'; then ensure each corresponding panel component/element uses role="tabpanel", id={`panel-${viewKey}`}, and aria-labelledby={`tab-${viewKey}`}. Keep using activeView() and setActiveView(...) for state logic.packages/devtools-seo/src/utils/tokens.ts (1)
208-208: Duplicate "sans-serif" in font family stack.The
sansfont family ends withsans-serif, sans-serifwhich is redundant.📝 Suggested fix
- sans: 'ui-sans-serif, Inter, system-ui, sans-serif, sans-serif', + sans: 'ui-sans-serif, Inter, system-ui, sans-serif',🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/devtools-seo/src/utils/tokens.ts` at line 208, The font family definition for the "sans" key in tokens.ts contains a duplicated fallback ("sans-serif, sans-serif"); update the sans value (the sans property) to remove the redundant "sans-serif" so the stack ends with a single "sans-serif" fallback (e.g., "ui-sans-serif, Inter, system-ui, sans-serif").
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/devtools-seo/src/tabs/json-ld-preview.tsx`:
- Line 343: The line computing raw may throw if script.textContent is null;
update the assignment that defines raw (the variable computed from
script.textContent in json-ld-preview.tsx) to safely handle null by using
optional chaining or a fallback before trimming (e.g., use
script.textContent?.trim() ?? '' or (script.textContent || '').trim()) so raw is
always a string and avoids a null dereference.
- Around line 602-603: The JsonLdPreviewSection currently calls
analyzeJsonLdScripts() directly which runs on every render and won't update
reactively when the DOM changes; change it to store the result in a signal
(e.g., const entries = useSignal(analyzeJsonLdScripts())) and subscribe to DOM
changes using the existing hooks used by SerpPreviewSection (useHeadChanges
and/or useLocationChanges) to recompute and set entries when changes occur; then
update component usages to call entries() and score() where needed so the UI
reacts to updates.
In `@packages/devtools-seo/src/tabs/serp-preview.tsx`:
- Line 343: The code currently calls script.textContent.trim(), which can throw
if textContent is null; update the logic around where script.textContent is used
(the variable named script in this file/tab) to guard against null by using a
null-coalescing or conditional check (e.g., const txt = (script.textContent ??
'').trim() or if (script.textContent) { const txt = script.textContent.trim();
... }) before calling trim(), ensuring downstream code uses the guarded txt
value.
---
Nitpick comments:
In `@packages/devtools-seo/src/tabs/seo-tab.tsx`:
- Around line 20-75: The tab buttons in seo-tab.tsx currently have role="tab"
and aria-selected but lack id and aria-controls, so add a unique id to each
button (e.g., id={`tab-${viewKey}`}) and set aria-controls to the matching panel
id (e.g., aria-controls={`panel-${viewKey}`}) where viewKey corresponds to
'overview', 'heading-structure', 'links-preview', 'social-previews',
'serp-preview', 'json-ld-preview'; then ensure each corresponding panel
component/element uses role="tabpanel", id={`panel-${viewKey}`}, and
aria-labelledby={`tab-${viewKey}`}. Keep using activeView() and
setActiveView(...) for state logic.
In `@packages/devtools-seo/src/utils/tokens.ts`:
- Line 208: The font family definition for the "sans" key in tokens.ts contains
a duplicated fallback ("sans-serif, sans-serif"); update the sans value (the
sans property) to remove the redundant "sans-serif" so the stack ends with a
single "sans-serif" fallback (e.g., "ui-sans-serif, Inter, system-ui,
sans-serif").
In `@packages/devtools-seo/src/utils/use-seo-styles.ts`:
- Around line 15-28: The css block for seoTabContainer contains a duplicate
property overflow-y: auto; — remove the redundant occurrence so overflow-y: auto
is declared only once in the seoTabContainer CSS template (locate the css
literal named seoTabContainer in use-seo-styles.ts and delete the
second/duplicate overflow-y line).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4999665e-9196-4950-8f50-6b219d900d09
📒 Files selected for processing (15)
packages/devtools-seo/src/index.tspackages/devtools-seo/src/solid-panel.tsxpackages/devtools-seo/src/tabs/heading-structure-preview.tsxpackages/devtools-seo/src/tabs/json-ld-preview.tsxpackages/devtools-seo/src/tabs/links-preview.tsxpackages/devtools-seo/src/tabs/seo-overview.tsxpackages/devtools-seo/src/tabs/seo-tab.tsxpackages/devtools-seo/src/tabs/serp-preview.tsxpackages/devtools-seo/src/tabs/social-previews.tsxpackages/devtools-seo/src/utils/canonical-url-data.tspackages/devtools-seo/src/utils/devtools-dom-filter.tspackages/devtools-seo/src/utils/seo-section-summary.tspackages/devtools-seo/src/utils/seo-severity.tspackages/devtools-seo/src/utils/tokens.tspackages/devtools-seo/src/utils/use-seo-styles.ts
✅ Files skipped from review due to trivial changes (2)
- packages/devtools-seo/src/index.ts
- packages/devtools-seo/src/solid-panel.tsx
…nstack-devtools into feat/seo-overview
…nstack-devtools into feat/seo-overview
🎯 Changes
Introduced new tabs in the SEO section:
(Sorry for the low quality video but GitHub didn't let me upload the high quality one)
SEO-tab-pr.mp4
✅ Checklist
pnpm test:pr.🚀 Release Impact
Summary by CodeRabbit
Release Notes
New Features
Updates