nuxt-seo-utils 8.2.1 → 8.3.1

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.
Files changed (49) hide show
  1. package/dist/cli.mjs +1 -1
  2. package/dist/devtools/lib/seo-utils/rpc.ts +3 -0
  3. package/dist/devtools/lib/seo-utils/state.ts +35 -0
  4. package/dist/devtools/lib/seo-utils/tools.ts +98 -0
  5. package/dist/devtools/nuxt.config.ts +3 -0
  6. package/dist/devtools/pages/seo-utils/debug.vue +50 -0
  7. package/dist/devtools/pages/seo-utils/docs.vue +3 -0
  8. package/dist/devtools/pages/seo-utils/identity.vue +1168 -0
  9. package/dist/devtools/pages/seo-utils/index.vue +609 -0
  10. package/dist/devtools/pages/seo-utils/social.vue +515 -0
  11. package/dist/devtools/pages/seo-utils.vue +64 -0
  12. package/dist/module.json +1 -1
  13. package/dist/runtime/app/composables/polyfills.js +1 -1
  14. package/dist/runtime/app/composables/useBreadcrumbItems.js +3 -3
  15. package/dist/runtime/app/composables/useFallbackTitle.js +1 -1
  16. package/dist/runtime/app/composables/useShareLinks.js +2 -2
  17. package/dist/runtime/app/logic/applyDefaults.js +2 -2
  18. package/dist/runtime/app/plugins/1.absoluteImageUrls.server.js +1 -1
  19. package/dist/runtime/app/plugins/siteConfig.js +1 -1
  20. package/dist/runtime/server/middleware/redirectCanonical.js +1 -1
  21. package/dist/runtime/server/middleware/resolveImagesInPagesDir.js +1 -1
  22. package/dist/runtime/server/routes/__nuxt-seo-utils/debug.js +1 -1
  23. package/package.json +32 -31
  24. package/dist/devtools/200.html +0 -1
  25. package/dist/devtools/404.html +0 -1
  26. package/dist/devtools/_nuxt/-x-fxsZ8.js +0 -33
  27. package/dist/devtools/_nuxt/9bFguHRm.js +0 -1
  28. package/dist/devtools/_nuxt/BFlHJz6O.js +0 -1
  29. package/dist/devtools/_nuxt/BgGLJ6MY.js +0 -14
  30. package/dist/devtools/_nuxt/BrktcVIY.js +0 -1
  31. package/dist/devtools/_nuxt/BvTRrGaN.js +0 -1
  32. package/dist/devtools/_nuxt/CwH3HOIZ.js +0 -1
  33. package/dist/devtools/_nuxt/DevtoolsMetric.DabK8i8g.css +0 -1
  34. package/dist/devtools/_nuxt/Drh0Ii4X.js +0 -1
  35. package/dist/devtools/_nuxt/builds/latest.json +0 -1
  36. package/dist/devtools/_nuxt/builds/meta/30a898c6-4010-4ba2-813b-88c7b3e82138.json +0 -1
  37. package/dist/devtools/_nuxt/debug.B9T5jmI-.css +0 -1
  38. package/dist/devtools/_nuxt/entry.aDw8hoj_.css +0 -1
  39. package/dist/devtools/_nuxt/fira-code.Bc8wnsZt.woff2 +0 -0
  40. package/dist/devtools/_nuxt/hubot-sans.DLGyhQVu.woff2 +0 -0
  41. package/dist/devtools/_nuxt/identity.Dl98KbLG.css +0 -1
  42. package/dist/devtools/_nuxt/index.BrQ8jYVK.css +0 -1
  43. package/dist/devtools/_nuxt/social.DrJTEVxX.css +0 -1
  44. package/dist/devtools/_nuxt/tools.DM91U8p4.css +0 -1
  45. package/dist/devtools/debug/index.html +0 -1
  46. package/dist/devtools/docs/index.html +0 -1
  47. package/dist/devtools/identity/index.html +0 -1
  48. package/dist/devtools/index.html +0 -1
  49. 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.2.1";
11
+ const version = "8.3.1";
12
12
  const description = "SEO utilities to improve your Nuxt sites discoverability and shareability";
13
13
 
14
14
  const ICON_SIZES = [
@@ -0,0 +1,3 @@
1
+ // The layer refreshes data on connect and on every host route change, so seo-utils
2
+ // needs no module-level host access.
3
+ useDevtoolsConnection()
@@ -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,3 @@
1
+ // Nuxt SEO devtools panel, shipped as a layer (Model C). The unified devtools client
2
+ // (assembled by nuxtseo-shared in the user's project) extends this to render /seo-utils.
3
+ export default defineNuxtConfig({})
@@ -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>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <DevtoolsDocs url="https://nuxtseo.com/docs/seo-utils" />
3
+ </template>