Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
0aef93e
refactor(devtools): remove SeoTab component and related functionality
abedshaaban Mar 7, 2026
6263ab7
refactor(devtools): rename SocialPreviewSection to SocialPreviewsSection
abedshaaban Mar 7, 2026
a2f1d81
refactor(devtools): rename social-preview file to social-previews and…
abedshaaban Mar 7, 2026
5682370
feat(devtools): add SEO tab navigation and enhance SocialPreviewsSection
abedshaaban Mar 7, 2026
98fa3c9
feat(devtools): enhance SERP and Social Previews with new styles and …
abedshaaban Mar 7, 2026
6c45bce
refactor(devtools): update SERP snippet styles and structure
abedshaaban Mar 7, 2026
f9046d1
feat(devtools): enhance SERP preview with truncation logic and overfl…
abedshaaban Mar 7, 2026
f053469
feat(devtools): add SERP overflow reporting and improve styles
abedshaaban Mar 7, 2026
e9caf21
feat(devtools): add meta description and default favicon styles
abedshaaban Mar 7, 2026
7009617
feat(devtools): enhance SERP preview layout with new styles
abedshaaban Mar 7, 2026
2a06f34
feat(devtools): enhance SERP preview for mobile with new styles and o…
abedshaaban Mar 7, 2026
01712ad
feat(devtools): implement comprehensive SERP issue reporting and enha…
abedshaaban Mar 7, 2026
cfa0fbb
refactor(devtools): update SERP preview truncation logic and improve …
abedshaaban Mar 7, 2026
1b7fdc3
refactor(devtools): streamline SERP preview component and improve cha…
abedshaaban Mar 7, 2026
7d2dc90
Merge branch 'main' into feat-seo-serp-preview
AlemTuzlak Mar 11, 2026
3d9ca40
refactor(devtools): reorder imports in social-previews component for …
abedshaaban Mar 11, 2026
d11c81e
Merge branch 'main' into feat-seo-serp-preview
abedshaaban Mar 11, 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/vast-apes-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/devtools': patch
---

Implemented a new SERP (Search Engine Results Page) section in the SEO tab. This update introduces desktop and mobile preview of search results. It displays the current site's favicon, title and description while displaying errors and issues when they are not found or they exceed the character limit.
5 changes: 5 additions & 0 deletions examples/react/basic/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
<meta name="twitter:url" content="https://example.com/basic" />
<script src="https://unpkg.com/react-scan/dist/auto.global.js"></script>
<title>Basic Example - TanStack Devtools</title>
<meta
name="description"
content="A basic example of using TanStack Devtools with React."
/>

<description
>A basic example of using TanStack Devtools with React.</description
>
Expand Down
159 changes: 159 additions & 0 deletions packages/devtools/src/styles/use-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,32 @@ const stylesFactory = (theme: DevtoolsStore['settings']['theme']) => {
margin-bottom: 2rem;
border-radius: 0.75rem;
`,
seoSubNav: css`
display: flex;
flex-direction: row;
gap: 0;
margin-bottom: 1rem;
border-bottom: 1px solid ${t(colors.gray[200], colors.gray[800])};
`,
seoSubNavLabel: css`
padding: 0.5rem 1rem;
font-size: 0.875rem;
font-weight: 500;
color: ${t(colors.gray[600], colors.gray[400])};
background: none;
border: none;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
cursor: pointer;
font-family: inherit;
&:hover {
color: ${t(colors.gray[800], colors.gray[200])};
}
`,
seoSubNavLabelActive: css`
color: ${t(colors.gray[900], colors.gray[100])};
border-bottom-color: ${t(colors.gray[900], colors.gray[100])};
`,
seoPreviewSection: css`
display: flex;
flex-direction: row;
Expand Down Expand Up @@ -205,6 +231,139 @@ const stylesFactory = (theme: DevtoolsStore['settings']['theme']) => {
padding: 0 10px 8px 10px;
font-size: 0.875rem;
`,
serpPreviewBlock: css`
margin-bottom: 1.5rem;
border: 1px solid ${t(colors.gray[200], colors.gray[700])};
border-radius: 10px;
padding: 1rem;
`,
serpPreviewLabel: css`
font-size: 0.875rem;
font-weight: 600;
margin-bottom: 0.5rem;
color: ${t(colors.gray[700], colors.gray[300])};
`,
serpSnippet: css`
border: 1px solid ${t(colors.gray[100], colors.gray[800])};
border-radius: 8px;
padding: 1rem 1.25rem;
background: ${t(colors.white, colors.darkGray[900])};
max-width: 600px;
font-family: ${fontFamily.sans};
box-shadow: 0 1px 2px ${t('rgba(0,0,0,0.04)', 'rgba(0,0,0,0.08)')};
`,
serpSnippetMobile: css`
border: 1px solid ${t(colors.gray[100], colors.gray[800])};
border-radius: 8px;
padding: 1rem 1.25rem;
background: ${t(colors.white, colors.darkGray[900])};
max-width: 380px;
font-family: ${fontFamily.sans};
box-shadow: 0 1px 2px ${t('rgba(0,0,0,0.04)', 'rgba(0,0,0,0.08)')};
`,
serpSnippetDescMobile: css`
font-size: 0.875rem;
color: ${t(colors.gray[700], colors.gray[300])};
margin: 0;
line-height: 1.5;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
`,
serpSnippetTopRow: css`
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 8px;
`,
serpSnippetFavicon: css`
width: 28px;
height: 28px;
border-radius: 50%;
flex-shrink: 0;
object-fit: contain;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
`,
serpSnippetDefaultFavicon: css`
width: 28px;
height: 28px;
background-color: ${t(colors.gray[200], colors.gray[800])};
border-radius: 50%;
flex-shrink: 0;
object-fit: contain;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
`,
serpSnippetSiteColumn: css`
display: flex;
flex-direction: column;
gap: 0;
min-width: 0;
`,
serpSnippetSiteName: css`
font-size: 0.875rem;
color: ${t(colors.gray[900], colors.gray[100])};
line-height: 1.4;
margin: 0;
`,
serpSnippetSiteUrl: css`
font-size: 0.75rem;
color: ${t(colors.gray[500], colors.gray[500])};
line-height: 1.4;
margin: 0;
`,
serpSnippetTitle: css`
font-size: 1.25rem;
font-weight: 400;
color: ${t('#1a0dab', '#8ab4f8')};
margin: 0 0 4px 0;
line-height: 1.3;
`,
serpSnippetDesc: css`
font-size: 0.875rem;
color: ${t(colors.gray[700], colors.gray[300])};
margin: 0;
line-height: 1.5;
`,
serpMeasureHidden: css`
position: absolute;
left: -9999px;
top: 0;
visibility: hidden;
pointer-events: none;
box-sizing: border-box;
`,
serpMeasureHiddenMobile: css`
position: absolute;
left: -9999px;
top: 0;
width: 340px;
visibility: hidden;
pointer-events: none;
font-size: 0.875rem;
line-height: 1.5;
`,
serpReportSection: css`
margin-top: 1rem;
font-size: 0.875rem;
color: ${t(colors.gray[700], colors.gray[300])};
`,
serpErrorList: css`
margin: 4px 0 0 0;
padding-left: 1.25rem;
list-style-type: disc;
`,
serpReportItem: css`
margin-top: 0.25rem;
color: ${t(colors.red[700], colors.red[400])};
font-size: 0.875rem;
`,
devtoolsPanelContainer: (
panelLocation: TanStackDevtoolsConfig['panelLocation'],
isDetached: boolean,
Expand Down
41 changes: 41 additions & 0 deletions packages/devtools/src/tabs/seo-tab/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Show, createSignal } from 'solid-js'
import { MainPanel } from '@tanstack/devtools-ui'
import { useStyles } from '../../styles/use-styles'
import { SocialPreviewsSection } from './social-previews'
import { SerpPreviewSection } from './serp-preview'

type SeoSubView = 'social-previews' | 'serp-preview'

export const SeoTab = () => {
const [activeView, setActiveView] =
createSignal<SeoSubView>('social-previews')
const styles = useStyles()

return (
<MainPanel withPadding>
<nav class={styles().seoSubNav} aria-label="SEO sections">
<button
type="button"
class={`${styles().seoSubNavLabel} ${activeView() === 'social-previews' ? styles().seoSubNavLabelActive : ''}`}
onClick={() => setActiveView('social-previews')}
>
Social previews
</button>
<button
type="button"
class={`${styles().seoSubNavLabel} ${activeView() === 'serp-preview' ? styles().seoSubNavLabelActive : ''}`}
onClick={() => setActiveView('serp-preview')}
>
SERP Preview
</button>
</nav>

<Show when={activeView() === 'social-previews'}>
<SocialPreviewsSection />
</Show>
<Show when={activeView() === 'serp-preview'}>
<SerpPreviewSection />
</Show>
</MainPanel>
)
}
Loading