Skip to content

Commit b2fed6f

Browse files
committed
feat: add json-ld website schema
1 parent 0805720 commit b2fed6f

2 files changed

Lines changed: 133 additions & 0 deletions

File tree

app/app.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ useHead({
1515
},
1616
})
1717
18+
if (import.meta.server) {
19+
setJsonLd(createWebSiteSchema())
20+
}
21+
1822
// Global keyboard shortcut: "/" focuses search or navigates to search page
1923
function handleGlobalKeydown(e: KeyboardEvent) {
2024
const target = e.target as HTMLElement

app/composables/useJsonLd.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// JSON-LD Schema Types
2+
interface JsonLdBase {
3+
'@context': 'https://schema.org'
4+
'@type': string
5+
}
6+
7+
interface WebSiteSchema extends JsonLdBase {
8+
'@type': 'WebSite'
9+
'name': string
10+
'url': string
11+
'description'?: string
12+
'potentialAction'?: SearchActionSchema
13+
}
14+
15+
interface SearchActionSchema {
16+
'@type': 'SearchAction'
17+
'target': {
18+
'@type': 'EntryPoint'
19+
'urlTemplate': string
20+
}
21+
'query-input': string
22+
}
23+
24+
interface SoftwareApplicationSchema extends JsonLdBase {
25+
'@type': 'SoftwareApplication'
26+
'name': string
27+
'description'?: string
28+
'applicationCategory': 'DeveloperApplication'
29+
'operatingSystem': 'Cross-platform'
30+
'url': string
31+
'softwareVersion'?: string
32+
'dateModified'?: string
33+
'datePublished'?: string
34+
'license'?: string
35+
'author'?: PersonSchema | OrganizationSchema | (PersonSchema | OrganizationSchema)[]
36+
'maintainer'?: PersonSchema | OrganizationSchema | (PersonSchema | OrganizationSchema)[]
37+
'offers'?: OfferSchema
38+
'downloadUrl'?: string
39+
'codeRepository'?: string
40+
'keywords'?: string[]
41+
}
42+
43+
interface PersonSchema extends JsonLdBase {
44+
'@type': 'Person'
45+
'name': string
46+
'url'?: string
47+
}
48+
49+
interface OrganizationSchema extends JsonLdBase {
50+
'@type': 'Organization'
51+
'name': string
52+
'url'?: string
53+
'logo'?: string
54+
'description'?: string
55+
'sameAs'?: string[]
56+
}
57+
58+
interface OfferSchema {
59+
'@type': 'Offer'
60+
'price': string
61+
'priceCurrency': string
62+
}
63+
64+
interface BreadcrumbListSchema extends JsonLdBase {
65+
'@type': 'BreadcrumbList'
66+
'itemListElement': BreadcrumbItemSchema[]
67+
}
68+
69+
interface BreadcrumbItemSchema {
70+
'@type': 'ListItem'
71+
'position': number
72+
'name': string
73+
'item'?: string
74+
}
75+
76+
interface ProfilePageSchema extends JsonLdBase {
77+
'@type': 'ProfilePage'
78+
'name': string
79+
'url': string
80+
'mainEntity': PersonSchema | OrganizationSchema
81+
}
82+
83+
type JsonLdSchema =
84+
| WebSiteSchema
85+
| SoftwareApplicationSchema
86+
| PersonSchema
87+
| OrganizationSchema
88+
| BreadcrumbListSchema
89+
| ProfilePageSchema
90+
91+
/**
92+
* Inject JSON-LD script into head
93+
*/
94+
export function setJsonLd(schema: JsonLdSchema | JsonLdSchema[]) {
95+
const schemas = Array.isArray(schema) ? schema : [schema]
96+
97+
useHead({
98+
script: schemas.map((s, i) => ({
99+
type: 'application/ld+json',
100+
innerHTML: JSON.stringify(s),
101+
key: `json-ld-${i}`,
102+
})),
103+
})
104+
}
105+
106+
/**
107+
* Create WebSite schema with search action
108+
*/
109+
export function createWebSiteSchema(options?: {
110+
name?: string
111+
description?: string
112+
}): WebSiteSchema {
113+
const siteUrl = 'https://npmx.dev'
114+
return {
115+
'@context': 'https://schema.org',
116+
'@type': 'WebSite',
117+
'name': options?.name ?? 'npmx',
118+
'url': siteUrl,
119+
'description': options?.description ?? 'A fast, modern browser for the npm registry',
120+
'potentialAction': {
121+
'@type': 'SearchAction',
122+
'target': {
123+
'@type': 'EntryPoint',
124+
'urlTemplate': `${siteUrl}/search?q={search_term_string}`,
125+
},
126+
'query-input': 'required name=search_term_string',
127+
},
128+
}
129+
}

0 commit comments

Comments
 (0)