Skip to content

Commit 98fa3c9

Browse files
committed
feat(devtools): enhance SERP and Social Previews with new styles and data handling
This commit introduces new styles for the SERP snippet and updates the SERP and Social Previews sections to dynamically display data from the current page's metadata. The SocialPreviewsSection and SerpPreviewSection components are refactored to improve clarity and functionality, removing unnecessary props and enhancing the user interface with better styling.
1 parent 5682370 commit 98fa3c9

File tree

4 files changed

+120
-47
lines changed

4 files changed

+120
-47
lines changed

packages/devtools/src/styles/use-styles.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,42 @@ const stylesFactory = (theme: DevtoolsStore['settings']['theme']) => {
231231
padding: 0 10px 8px 10px;
232232
font-size: 0.875rem;
233233
`,
234+
serpSnippet: css`
235+
border: 1px solid ${t(colors.gray[200], colors.gray[800])};
236+
border-radius: 8px;
237+
padding: 1rem 1.25rem;
238+
background: ${t(colors.white, colors.darkGray[900])};
239+
max-width: 600px;
240+
font-family: ${fontFamily.sans};
241+
`,
242+
serpSnippetUrlRow: css`
243+
display: flex;
244+
align-items: center;
245+
gap: 6px;
246+
margin-bottom: 4px;
247+
font-size: 0.875rem;
248+
color: ${t(colors.gray[600], colors.gray[400])};
249+
`,
250+
serpSnippetFavicon: css`
251+
width: 16px;
252+
height: 16px;
253+
border-radius: 2px;
254+
flex-shrink: 0;
255+
object-fit: contain;
256+
`,
257+
serpSnippetTitle: css`
258+
font-size: 1.25rem;
259+
font-weight: 400;
260+
color: ${t('#1a0dab', '#8ab4f8')};
261+
margin: 0 0 4px 0;
262+
line-height: 1.3;
263+
`,
264+
serpSnippetDesc: css`
265+
font-size: 0.875rem;
266+
color: ${t(colors.gray[700], colors.gray[300])};
267+
margin: 0;
268+
line-height: 1.5;
269+
`,
234270
devtoolsPanelContainer: (
235271
panelLocation: TanStackDevtoolsConfig['panelLocation'],
236272
isDetached: boolean,

packages/devtools/src/tabs/seo-tab/index.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import { SerpPreviewSection } from './serp-preview'
77
type SeoSubView = 'social-previews' | 'serp-preview'
88

99
export const SeoTab = () => {
10-
const [activeView, setActiveView] = createSignal<SeoSubView>('social-previews')
10+
const [activeView, setActiveView] =
11+
createSignal<SeoSubView>('social-previews')
1112
const styles = useStyles()
1213

1314
return (
@@ -28,11 +29,12 @@ export const SeoTab = () => {
2829
SERP Preview
2930
</button>
3031
</nav>
32+
3133
<Show when={activeView() === 'social-previews'}>
32-
<SocialPreviewsSection noTitle />
34+
<SocialPreviewsSection />
3335
</Show>
3436
<Show when={activeView() === 'serp-preview'}>
35-
<SerpPreviewSection noTitle />
37+
<SerpPreviewSection />
3638
</Show>
3739
</MainPanel>
3840
)

packages/devtools/src/tabs/seo-tab/serp-preview.tsx

Lines changed: 77 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,86 @@
1-
import {
2-
Section,
3-
SectionDescription,
4-
SectionIcon,
5-
SectionTitle,
6-
} from '@tanstack/devtools-ui'
7-
import { PageSearch } from '@tanstack/devtools-ui/icons'
8-
9-
const DUMMY_SERP = {
10-
title: 'Example Page Title - Your Site Name',
11-
description:
12-
'This is a short meta description that shows how your page might appear in Google search results. Keep it under 160 characters.',
13-
url: 'https://example.com/page-path',
1+
import { createSignal } from 'solid-js'
2+
import { useStyles } from '../../styles/use-styles'
3+
import { useHeadChanges } from '../../hooks/use-head-changes'
4+
import { Section, SectionDescription } from '@tanstack/devtools-ui'
5+
6+
type SerpData = {
7+
title: string
8+
description: string
9+
siteName: string
10+
favicon: string | null
11+
url: string
1412
}
1513

16-
export function SerpPreviewSection(props: { noTitle?: boolean } = {}) {
14+
function getSerpFromHead(): SerpData {
15+
const title = document.title || ''
16+
const url = typeof window !== 'undefined' ? window.location.href : ''
17+
18+
const metaTags = Array.from(document.head.querySelectorAll('meta'))
19+
const descriptionMeta = metaTags.find(
20+
(m) => m.getAttribute('name')?.toLowerCase() === 'description',
21+
)
22+
const description =
23+
descriptionMeta?.getAttribute('content')?.trim() || ''
24+
25+
const siteNameMeta = metaTags.find(
26+
(m) => m.getAttribute('property') === 'og:site_name',
27+
)
28+
const siteName =
29+
siteNameMeta?.getAttribute('content')?.trim() ||
30+
(typeof window !== 'undefined'
31+
? window.location.hostname.replace(/^www\./, '')
32+
: '')
33+
34+
const linkTags = Array.from(document.head.querySelectorAll('link'))
35+
const iconLink = linkTags.find(
36+
(l) =>
37+
l.getAttribute('rel')?.toLowerCase().split(/\s+/).includes('icon'),
38+
)
39+
let favicon: string | null = iconLink?.getAttribute('href') || null
40+
if (favicon && typeof window !== 'undefined') {
41+
try {
42+
favicon = new URL(favicon, url).href
43+
} catch {
44+
favicon = null
45+
}
46+
}
47+
48+
return { title, description, siteName, favicon, url }
49+
}
50+
51+
export function SerpPreviewSection() {
52+
const [serp, setSerp] = createSignal<SerpData>(getSerpFromHead())
53+
const styles = useStyles()
54+
55+
useHeadChanges(() => {
56+
setSerp(getSerpFromHead())
57+
})
58+
59+
const data = serp()
60+
1761
return (
1862
<Section>
19-
{!props.noTitle && (
20-
<SectionTitle>
21-
<SectionIcon>
22-
<PageSearch />
23-
</SectionIcon>
24-
SERP Preview
25-
</SectionTitle>
26-
)}
2763
<SectionDescription>
28-
See how your title tag and meta description to see your website's SERP
29-
snippet preview in Google search results.
64+
See how your title tag and meta description may look in Google search
65+
results. Data is read from the current page.
3066
</SectionDescription>
31-
<div>
32-
<p>{DUMMY_SERP.title}</p>
33-
<p>{DUMMY_SERP.description}</p>
34-
<p>{DUMMY_SERP.url}</p>
67+
<div class={styles().serpSnippet}>
68+
<div class={styles().serpSnippetUrlRow}>
69+
{data.favicon ? (
70+
<img
71+
src={data.favicon}
72+
alt=""
73+
class={styles().serpSnippetFavicon}
74+
/>
75+
) : null}
76+
<span>{data.siteName || data.url}</span>
77+
</div>
78+
<div class={styles().serpSnippetTitle}>
79+
{data.title || 'No title'}
80+
</div>
81+
<div class={styles().serpSnippetDesc}>
82+
{data.description || 'No meta description.'}
83+
</div>
3584
</div>
3685
</Section>
3786
)

packages/devtools/src/tabs/seo-tab/social-previews.tsx

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
11
import { createSignal, For } from 'solid-js'
22
import { useStyles } from '../../styles/use-styles'
33
import { useHeadChanges } from '../../hooks/use-head-changes'
4-
import {
5-
Section,
6-
SectionDescription,
7-
SectionIcon,
8-
SectionTitle,
9-
} from '@tanstack/devtools-ui'
10-
import { SocialBubble } from '@tanstack/devtools-ui/icons'
4+
import { Section, SectionDescription } from '@tanstack/devtools-ui'
115

126
const SOCIALS = [
137
{
@@ -146,7 +140,7 @@ function SocialPreview(props: {
146140
)
147141
}
148142

149-
export function SocialPreviewsSection(props: { noTitle?: boolean } = {}) {
143+
export function SocialPreviewsSection() {
150144
const [reports, setReports] = createSignal<Array<SocialReport>>(analyzeHead())
151145
const styles = useStyles()
152146

@@ -184,14 +178,6 @@ export function SocialPreviewsSection(props: { noTitle?: boolean } = {}) {
184178

185179
return (
186180
<Section>
187-
{!props.noTitle && (
188-
<SectionTitle>
189-
<SectionIcon>
190-
<SocialBubble />
191-
</SectionIcon>
192-
Social previews
193-
</SectionTitle>
194-
)}
195181
<SectionDescription>
196182
See how your current page will look when shared on popular social
197183
networks. The tool checks for essential meta tags and highlights any

0 commit comments

Comments
 (0)