nuxt-seo-utils 8.2.0 → 8.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +1 -1
- package/dist/devtools/lib/seo-utils/rpc.ts +3 -0
- package/dist/devtools/lib/seo-utils/state.ts +35 -0
- package/dist/devtools/lib/seo-utils/tools.ts +98 -0
- package/dist/devtools/nuxt.config.ts +3 -0
- package/dist/devtools/pages/seo-utils/debug.vue +50 -0
- package/dist/devtools/pages/seo-utils/docs.vue +3 -0
- package/dist/devtools/pages/seo-utils/identity.vue +1168 -0
- package/dist/devtools/pages/seo-utils/index.vue +609 -0
- package/dist/devtools/pages/seo-utils/social.vue +515 -0
- package/dist/devtools/pages/seo-utils.vue +64 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +2 -2
- package/dist/runtime/app/composables/polyfills.js +1 -1
- package/dist/runtime/app/composables/useBreadcrumbItems.js +3 -3
- package/dist/runtime/app/composables/useFallbackTitle.js +1 -1
- package/dist/runtime/app/composables/useShareLinks.js +2 -2
- package/dist/runtime/app/logic/applyDefaults.js +2 -2
- package/dist/runtime/app/plugins/1.absoluteImageUrls.server.js +1 -1
- package/dist/runtime/app/plugins/siteConfig.js +1 -1
- package/dist/runtime/server/middleware/redirectCanonical.js +1 -1
- package/dist/runtime/server/middleware/resolveImagesInPagesDir.js +1 -1
- package/dist/runtime/server/routes/__nuxt-seo-utils/debug.d.ts +1 -1
- package/dist/runtime/server/routes/__nuxt-seo-utils/debug.js +1 -1
- package/package.json +32 -31
- package/dist/devtools/200.html +0 -1
- package/dist/devtools/404.html +0 -1
- package/dist/devtools/_nuxt/BEw_EXq1.js +0 -14
- package/dist/devtools/_nuxt/BgCgKrEE.js +0 -1
- package/dist/devtools/_nuxt/C4gkMyix.js +0 -1
- package/dist/devtools/_nuxt/CQ4Jy7Nm.js +0 -1
- package/dist/devtools/_nuxt/DcgcSEa-.js +0 -33
- package/dist/devtools/_nuxt/DeIiaCYF.js +0 -1
- package/dist/devtools/_nuxt/DevtoolsMetric.DabK8i8g.css +0 -1
- package/dist/devtools/_nuxt/DfZjB7w2.js +0 -1
- package/dist/devtools/_nuxt/DjEWf-n1.js +0 -1
- package/dist/devtools/_nuxt/builds/latest.json +0 -1
- package/dist/devtools/_nuxt/builds/meta/21c3fe25-8926-44ca-9d37-f478243d148a.json +0 -1
- package/dist/devtools/_nuxt/debug.B9T5jmI-.css +0 -1
- package/dist/devtools/_nuxt/entry.aDw8hoj_.css +0 -1
- package/dist/devtools/_nuxt/fira-code.Bc8wnsZt.woff2 +0 -0
- package/dist/devtools/_nuxt/hubot-sans.DLGyhQVu.woff2 +0 -0
- package/dist/devtools/_nuxt/identity.Dl98KbLG.css +0 -1
- package/dist/devtools/_nuxt/index.BrQ8jYVK.css +0 -1
- package/dist/devtools/_nuxt/social.DrJTEVxX.css +0 -1
- package/dist/devtools/_nuxt/tools.DM91U8p4.css +0 -1
- package/dist/devtools/debug/index.html +0 -1
- package/dist/devtools/docs/index.html +0 -1
- package/dist/devtools/identity/index.html +0 -1
- package/dist/devtools/index.html +0 -1
- package/dist/devtools/social/index.html +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { colors } from 'consola/utils';
|
|
|
8
8
|
import { resolve } from 'pathe';
|
|
9
9
|
|
|
10
10
|
const name = "nuxt-seo-utils";
|
|
11
|
-
const version = "8.
|
|
11
|
+
const version = "8.3.0";
|
|
12
12
|
const description = "SEO utilities to improve your Nuxt sites discoverability and shareability";
|
|
13
13
|
|
|
14
14
|
const ICON_SIZES = [
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ref, watch } from 'vue'
|
|
2
|
+
|
|
3
|
+
export interface DebugData {
|
|
4
|
+
runtimeConfig: { version: string, [key: string]: any }
|
|
5
|
+
siteConfig: {
|
|
6
|
+
url: string
|
|
7
|
+
name: string
|
|
8
|
+
description: string
|
|
9
|
+
env: string
|
|
10
|
+
indexable: boolean
|
|
11
|
+
trailingSlash: boolean
|
|
12
|
+
titleSeparator: string
|
|
13
|
+
defaultLocale: string
|
|
14
|
+
currentLocale: string
|
|
15
|
+
twitter: string
|
|
16
|
+
}
|
|
17
|
+
headConfig: {
|
|
18
|
+
link: Array<{ rel: string, href: string, type?: string, sizes?: string, media?: string }>
|
|
19
|
+
meta: Array<{ name?: string, property?: string, content?: string }>
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const data = ref<DebugData | null>(null)
|
|
24
|
+
|
|
25
|
+
export async function refreshSources() {
|
|
26
|
+
if (!appFetch.value)
|
|
27
|
+
return
|
|
28
|
+
data.value = await appFetch.value('/__nuxt-seo-utils/debug.json', {
|
|
29
|
+
query: { path: path.value },
|
|
30
|
+
}).catch(() => null)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
watch([path, appFetch, refreshTime], () => {
|
|
34
|
+
refreshSources()
|
|
35
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export const SEO_LIMITS = {
|
|
2
|
+
TITLE_MAX_CHARS: 60,
|
|
3
|
+
TITLE_WARN_CHARS: 50,
|
|
4
|
+
TITLE_MAX_PIXELS: 580,
|
|
5
|
+
DESC_MAX_CHARS: 160,
|
|
6
|
+
DESC_WARN_CHARS: 150,
|
|
7
|
+
DESC_MAX_PIXELS: 920,
|
|
8
|
+
} as const
|
|
9
|
+
|
|
10
|
+
export function estimatePixelWidth(text: string, fontSize: number = 16): number {
|
|
11
|
+
return Math.round(text.length * fontSize * 0.55)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function parseMetaTags(html: string) {
|
|
15
|
+
const parser = new DOMParser()
|
|
16
|
+
const doc = parser.parseFromString(html, 'text/html')
|
|
17
|
+
|
|
18
|
+
const title = doc.querySelector('title')?.textContent?.trim() || ''
|
|
19
|
+
const description = doc.querySelector('meta[name="description"]')?.getAttribute('content')?.trim() || ''
|
|
20
|
+
const canonical = doc.querySelector('link[rel="canonical"]')?.getAttribute('href') || ''
|
|
21
|
+
|
|
22
|
+
const ogTags: Record<string, string> = {}
|
|
23
|
+
doc.querySelectorAll('meta[property^="og:"]').forEach((el) => {
|
|
24
|
+
const prop = el.getAttribute('property')
|
|
25
|
+
const content = el.getAttribute('content')
|
|
26
|
+
if (prop && content)
|
|
27
|
+
ogTags[prop] = content
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const twitterTags: Record<string, string> = {}
|
|
31
|
+
doc.querySelectorAll('meta[name^="twitter:"]').forEach((el) => {
|
|
32
|
+
const name = el.getAttribute('name')
|
|
33
|
+
const content = el.getAttribute('content')
|
|
34
|
+
if (name && content)
|
|
35
|
+
twitterTags[name] = content
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const allMeta: { name: string, content: string, type: 'name' | 'property' | 'http-equiv', media?: string }[] = []
|
|
39
|
+
doc.querySelectorAll('meta').forEach((el) => {
|
|
40
|
+
const name = el.getAttribute('name')
|
|
41
|
+
const property = el.getAttribute('property')
|
|
42
|
+
const httpEquiv = el.getAttribute('http-equiv')
|
|
43
|
+
const content = el.getAttribute('content') || ''
|
|
44
|
+
const media = el.getAttribute('media') || undefined
|
|
45
|
+
|
|
46
|
+
if (name)
|
|
47
|
+
allMeta.push({ name, content, type: 'name', media })
|
|
48
|
+
else if (property)
|
|
49
|
+
allMeta.push({ name: property, content, type: 'property', media })
|
|
50
|
+
else if (httpEquiv)
|
|
51
|
+
allMeta.push({ name: httpEquiv, content, type: 'http-equiv', media })
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const schemas: Array<{ type: string, data: any, raw: string }> = []
|
|
55
|
+
doc.querySelectorAll('script[type="application/ld+json"]').forEach((el) => {
|
|
56
|
+
const raw = el.textContent?.trim() || ''
|
|
57
|
+
try {
|
|
58
|
+
const data = JSON.parse(raw)
|
|
59
|
+
const type = data['@type'] || (data['@graph'] ? 'Graph' : 'Unknown')
|
|
60
|
+
schemas.push({ type, data, raw })
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
schemas.push({ type: 'Invalid JSON', data: null, raw })
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const iconLinks: Array<{ rel: string, href: string, type?: string, sizes?: string, media?: string }> = []
|
|
68
|
+
doc.querySelectorAll('link[rel="icon"], link[rel="apple-touch-icon"], link[rel="shortcut icon"]').forEach((el) => {
|
|
69
|
+
const href = el.getAttribute('href')
|
|
70
|
+
if (!href)
|
|
71
|
+
return
|
|
72
|
+
iconLinks.push({
|
|
73
|
+
rel: el.getAttribute('rel') || 'icon',
|
|
74
|
+
href,
|
|
75
|
+
type: el.getAttribute('type') || undefined,
|
|
76
|
+
sizes: el.getAttribute('sizes') || undefined,
|
|
77
|
+
media: el.getAttribute('media') || undefined,
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
return { title, description, ogTags, twitterTags, allMeta, canonical, schemas, iconLinks }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function titleColor(length: number) {
|
|
85
|
+
if (length > SEO_LIMITS.TITLE_MAX_CHARS)
|
|
86
|
+
return 'error'
|
|
87
|
+
if (length < 30)
|
|
88
|
+
return 'warning'
|
|
89
|
+
return 'success'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function descColor(length: number) {
|
|
93
|
+
if (length > SEO_LIMITS.DESC_MAX_CHARS)
|
|
94
|
+
return 'error'
|
|
95
|
+
if (length > SEO_LIMITS.DESC_WARN_CHARS || length < 70)
|
|
96
|
+
return 'warning'
|
|
97
|
+
return 'success'
|
|
98
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import { data } from '../../lib/seo-utils/state'
|
|
4
|
+
|
|
5
|
+
const runtimeConfigItems = computed(() => {
|
|
6
|
+
if (!data.value?.runtimeConfig)
|
|
7
|
+
return []
|
|
8
|
+
return Object.entries(data.value.runtimeConfig).map(([key, value]) => ({
|
|
9
|
+
key,
|
|
10
|
+
value: typeof value === 'object' ? JSON.stringify(value) : String(value),
|
|
11
|
+
mono: true,
|
|
12
|
+
}))
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
const siteConfigItems = computed(() => {
|
|
16
|
+
if (!data.value?.siteConfig)
|
|
17
|
+
return []
|
|
18
|
+
return Object.entries(data.value.siteConfig).map(([key, value]) => ({
|
|
19
|
+
key,
|
|
20
|
+
value: String(value),
|
|
21
|
+
mono: true,
|
|
22
|
+
}))
|
|
23
|
+
})
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<template>
|
|
27
|
+
<div class="space-y-5 stagger-children">
|
|
28
|
+
<DevtoolsSection icon="carbon:settings" text="Runtime Config">
|
|
29
|
+
<template #actions>
|
|
30
|
+
<DevtoolsMetric :value="runtimeConfigItems.length" label="keys" />
|
|
31
|
+
</template>
|
|
32
|
+
<DevtoolsKeyValue :items="runtimeConfigItems" striped />
|
|
33
|
+
</DevtoolsSection>
|
|
34
|
+
|
|
35
|
+
<DevtoolsSection icon="carbon:earth" text="Site Config">
|
|
36
|
+
<template #actions>
|
|
37
|
+
<DevtoolsMetric :value="siteConfigItems.length" label="keys" />
|
|
38
|
+
</template>
|
|
39
|
+
<DevtoolsKeyValue :items="siteConfigItems" striped />
|
|
40
|
+
</DevtoolsSection>
|
|
41
|
+
|
|
42
|
+
<DevtoolsSection icon="carbon:code" text="Raw JSON">
|
|
43
|
+
<DevtoolsSnippet
|
|
44
|
+
:code="JSON.stringify(data || {}, null, 2)"
|
|
45
|
+
lang="json"
|
|
46
|
+
label="debug.json"
|
|
47
|
+
/>
|
|
48
|
+
</DevtoolsSection>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|