Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c43bcb2
feat(devtools): add SEO tab documentation with detailed features and …
abedshaaban Mar 31, 2026
ffb1f35
feat(devtools): add SEO analysis features including JSON-LD, heading …
abedshaaban Mar 31, 2026
276b2ef
refactor(devtools): unify SEO severity handling across components
abedshaaban Mar 31, 2026
7432b11
feat(devtools): enhance SEO tab with canonical and robots meta tags
abedshaaban Apr 1, 2026
e52378c
feat(devtools): introduce SEO overview section with comprehensive ana…
abedshaaban Apr 1, 2026
23e0197
feat(devtools): enhance SEO tab with new styles and structured issue …
abedshaaban Apr 1, 2026
10e295e
feat(devtools): enhance SEO tab with new styles and severity handling
abedshaaban Apr 1, 2026
13e48a8
feat(devtools): update SEO tab styles and improve health score visual…
abedshaaban Apr 1, 2026
1d51b04
feat(devtools): implement accordion-style links preview in SEO tab
abedshaaban Apr 1, 2026
38a1d32
feat(devtools): enhance JSON-LD preview with new styling and validati…
abedshaaban Apr 1, 2026
ae90128
feat(devtools): enhance SEO overview with new scoring and styling fea…
abedshaaban Apr 1, 2026
48b5788
feat(devtools): add navigation and display for heading structure and …
abedshaaban Apr 1, 2026
bad6287
feat(devtools): enhance seoSubNav styles for improved responsiveness …
abedshaaban Apr 1, 2026
d898203
feat(devtools): update package.json scripts and enhance JSON-LD and l…
abedshaaban Apr 1, 2026
ea5e33c
refactor(devtools): clean up imports and improve text handling in SEO…
abedshaaban Apr 1, 2026
566271b
refactor(devtools): simplify link classification logic in links previ…
abedshaaban Apr 2, 2026
9323504
refactor(devtools): remove unused SEO overview footnote and clean up …
abedshaaban Apr 2, 2026
63e23e2
chore(devtools): update size limit for devtools package in package.json
abedshaaban Apr 2, 2026
b923405
refactor(devtools): standardize capitalization and improve formatting…
abedshaaban Apr 2, 2026
26e639e
refactor(devtools): update section titles for clarity in SEO overview
abedshaaban Apr 2, 2026
4aee218
feat(devtools): introduce new SEO tab with live previews and structur…
abedshaaban Apr 2, 2026
ec1a257
ci: apply automated fixes
autofix-ci[bot] Apr 2, 2026
dbf8ee2
refactor(devtools): update type exports for CanonicalPageIssue and Ca…
abedshaaban Apr 2, 2026
a2bb1a3
Merge branch 'main' of https://github.com/abedshaaban/tanstack-devtools
abedshaaban Apr 2, 2026
d73db33
Merge branch 'main' into main
abedshaaban Apr 2, 2026
25ec4d7
refactor(devtools): enhance SEO tab components by removing canonical …
abedshaaban Apr 4, 2026
8ff18e7
feat(devtools): add useLocationChanges hook for location change detec…
abedshaaban Apr 4, 2026
0f4f1f9
refactor(devtools): streamline imports in links-preview component
abedshaaban Apr 4, 2026
9a785e8
fix(devtools): add canonical link to basic example HTML for improved SEO
abedshaaban Apr 4, 2026
5d5de5f
Merge branch 'main' of https://github.com/abedshaaban/tanstack-devtools
abedshaaban Apr 4, 2026
2fc42a1
fix(devtools): handle empty JSON-LD script content gracefully
abedshaaban Apr 4, 2026
a197f69
fix(devtools): remove optional chaining for textContent in SEO tab co…
abedshaaban Apr 4, 2026
9e1ab03
fix(devtools): adjust max-width values in use-styles and enhance SERP…
abedshaaban Apr 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/puny-games-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/devtools': patch
---

Introduce a new SEO tab in devtools: live head-driven social and SERP previews, structured data (JSON-LD), heading and link analysis, plus an overview that scores and links into each section.
12 changes: 12 additions & 0 deletions examples/react/basic/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,23 @@
content="A basic example of using TanStack Devtools with React."
/>

<meta name="robots" content="index, follow" />
<link rel="canonical" href="http://localhost:3005" />
Comment on lines +36 to +37
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't ship a localhost canonical URL.

Line 37 hard-codes http://localhost:3005, so the published example will canonicalize to the wrong origin outside local dev. That gives crawlers — and this new SEO tab — a false signal. Use the deployed example URL here, or drop the canonical tag from this fixture.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/react/basic/index.html` around lines 36 - 37, The canonical link in
examples/react/basic/index.html currently hard-codes "http://localhost:3005"
which will mislead crawlers; update the <link rel="canonical"> element (or
remove it) so it does not point to localhost—either replace the hard-coded URL
with the deployed example's public URL or remove the canonical tag entirely from
the fixture; ensure the change is applied to the <link rel="canonical"> element
so the example no longer signals a localhost origin.


<description
>A basic example of using TanStack Devtools with React.</description
>
</head>
<body>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "TanStack Devtools",
"url": "https://tanstack.com/devtools",
"logo": "https://tanstack.com/devtools/logo.png"
}
</script>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"size-limit": [
{
"path": "packages/devtools/dist/index.js",
"limit": "60 KB"
"limit": "69 KB"
},
{
"path": "packages/event-bus-client/dist/esm/plugin.js",
Expand Down
69 changes: 69 additions & 0 deletions packages/devtools/src/hooks/use-location-changes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { onCleanup, onMount } from 'solid-js'

const LOCATION_CHANGE_EVENT = 'tanstack-devtools:locationchange'

type LocationChangeListener = () => void

const listeners = new Set<LocationChangeListener>()

let lastHref = ''
let teardownLocationObservation: (() => void) | undefined

function emitLocationChangeIfNeeded() {
const nextHref = window.location.href
if (nextHref === lastHref) return
lastHref = nextHref
listeners.forEach((listener) => listener())
}

function dispatchLocationChangeEvent() {
window.dispatchEvent(new Event(LOCATION_CHANGE_EVENT))
}

function observeLocationChanges() {
if (teardownLocationObservation) return

lastHref = window.location.href

const originalPushState = window.history.pushState
const originalReplaceState = window.history.replaceState

const handleLocationSignal = () => {
emitLocationChangeIfNeeded()
}

window.history.pushState = function (...args) {
originalPushState.apply(this, args)
dispatchLocationChangeEvent()
}

window.history.replaceState = function (...args) {
originalReplaceState.apply(this, args)
dispatchLocationChangeEvent()
}

window.addEventListener('popstate', handleLocationSignal)
window.addEventListener('hashchange', handleLocationSignal)
window.addEventListener(LOCATION_CHANGE_EVENT, handleLocationSignal)

teardownLocationObservation = () => {
window.history.pushState = originalPushState
window.history.replaceState = originalReplaceState
window.removeEventListener('popstate', handleLocationSignal)
window.removeEventListener('hashchange', handleLocationSignal)
window.removeEventListener(LOCATION_CHANGE_EVENT, handleLocationSignal)
teardownLocationObservation = undefined
}
Comment on lines +28 to +56
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Only restore history methods if this hook still owns them.

Lines 50-51 unconditionally put back the originals. If something else wraps history.pushState or history.replaceState after this observer starts, tearing down the last listener will silently remove that newer wrapper and can break routing or analytics hooks in the host app.

🛠️ Suggested fix
-  window.history.pushState = function (...args) {
+  const pushStateWrapper: History['pushState'] = function (...args) {
     originalPushState.apply(this, args)
     dispatchLocationChangeEvent()
   }
+  window.history.pushState = pushStateWrapper

-  window.history.replaceState = function (...args) {
+  const replaceStateWrapper: History['replaceState'] = function (...args) {
     originalReplaceState.apply(this, args)
     dispatchLocationChangeEvent()
   }
+  window.history.replaceState = replaceStateWrapper
...
   teardownLocationObservation = () => {
-    window.history.pushState = originalPushState
-    window.history.replaceState = originalReplaceState
+    if (window.history.pushState === pushStateWrapper) {
+      window.history.pushState = originalPushState
+    }
+    if (window.history.replaceState === replaceStateWrapper) {
+      window.history.replaceState = originalReplaceState
+    }
     window.removeEventListener('popstate', handleLocationSignal)
     window.removeEventListener('hashchange', handleLocationSignal)
     window.removeEventListener(LOCATION_CHANGE_EVENT, handleLocationSignal)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/devtools/src/hooks/use-location-changes.ts` around lines 28 - 56,
The teardown unconditionally restores originalPushState/originalReplaceState
which can clobber newer wrappers; instead capture the wrapper functions when
installing them (e.g. assign wrappedPushState and wrappedReplaceState when you
set window.history.pushState/replaceState) and, in teardownLocationObservation,
only restore the originals if the current window.history.pushState ===
wrappedPushState (and similarly for replaceState); leave the current values
alone otherwise and still remove the event listeners and clear
teardownLocationObservation.

}

export function useLocationChanges(onChange: () => void) {
onMount(() => {
observeLocationChanges()
listeners.add(onChange)

onCleanup(() => {
listeners.delete(onChange)
if (listeners.size === 0) teardownLocationObservation?.()
})
})
}
Loading
Loading