|
1 | 1 | --- |
2 | | -import contactData from '@content/contact.json' |
3 | | -import { absoluteUrl } from '@components/scripts/utils/absoluteUrl' |
4 | | -
|
5 | | -export interface Props { |
6 | | - path: string |
7 | | - pageTitle: string |
8 | | - description?: string |
9 | | - contentType?: 'article' | 'website' |
10 | | - publishDate?: Date |
11 | | - modifiedDate?: Date |
12 | | - author?: string |
13 | | - image?: string |
14 | | -} |
| 2 | +import { getSchemas } from '@components/Head/server/structuredData' |
| 3 | +export type { StructuredDataProps as Props } from '@components/Head/server/structuredData' |
15 | 4 |
|
16 | 5 | const { |
17 | 6 | path, |
18 | 7 | pageTitle, |
19 | 8 | description, |
20 | | - contentType = 'website', |
| 9 | + contentType, |
21 | 10 | publishDate, |
22 | 11 | modifiedDate, |
23 | 12 | author, |
24 | 13 | image, |
25 | 14 | } = Astro.props |
26 | 15 |
|
27 | | -const baseUrl = Astro.site?.href ?? 'https://webstackbuilders.com' |
28 | | -const currentUrl = absoluteUrl(path, Astro.site) |
29 | | -
|
30 | | -// Organization schema - always included |
31 | | -const organizationSchema = { |
32 | | - '@context': 'https://schema.org', |
33 | | - '@type': 'Organization', |
34 | | - 'name': contactData.company.name, |
35 | | - 'url': contactData.company.url, |
36 | | - 'logo': absoluteUrl('/public/images/logos/company-card.png', Astro.site), |
37 | | - 'description': contactData.company.description, |
38 | | - 'email': contactData.company.email, |
39 | | - 'address': { |
40 | | - '@type': 'PostalAddress', |
41 | | - 'addressLocality': contactData.company.city, |
42 | | - 'addressRegion': contactData.company.state, |
43 | | - 'addressCountry': contactData.company.country, |
44 | | - }, |
45 | | - 'sameAs': contactData.company.social.map(social => social.url), |
46 | | -} |
47 | | -
|
48 | | -// WebSite schema - include on homepage |
49 | | -const webSiteSchema = path === '/' || path === '' ? { |
50 | | - '@context': 'https://schema.org', |
51 | | - '@type': 'WebSite', |
52 | | - 'name': contactData.company.name, |
53 | | - 'url': contactData.company.url, |
54 | | - 'description': contactData.company.description, |
55 | | - 'publisher': { |
56 | | - '@type': 'Organization', |
57 | | - 'name': contactData.company.name, |
58 | | - 'logo': { |
59 | | - '@type': 'ImageObject', |
60 | | - 'url': absoluteUrl('icon-512.png', Astro.site), |
61 | | - }, |
62 | | - }, |
63 | | -} : null |
64 | | -
|
65 | | -// Article schema - for blog posts |
66 | | -const articleSchema = contentType === 'article' && publishDate ? { |
67 | | - '@context': 'https://schema.org', |
68 | | - '@type': 'Article', |
69 | | - 'headline': pageTitle, |
70 | | - 'description': description || contactData.company.description, |
71 | | - 'datePublished': publishDate.toISOString(), |
72 | | - 'dateModified': modifiedDate?.toISOString() || publishDate.toISOString(), |
73 | | - 'author': { |
74 | | - '@type': 'Person', |
75 | | - 'name': author || contactData.company.author.name, |
76 | | - }, |
77 | | - 'publisher': { |
78 | | - '@type': 'Organization', |
79 | | - 'name': contactData.company.name, |
80 | | - 'logo': { |
81 | | - '@type': 'ImageObject', |
82 | | - 'url': absoluteUrl('icon-512.png', Astro.site), |
83 | | - }, |
84 | | - }, |
85 | | - 'url': currentUrl, |
86 | | - ...(image && { 'image': absoluteUrl(image, Astro.site) }), |
87 | | -} : null |
88 | | -
|
89 | | -// BreadcrumbList schema - for deep pages (2+ levels) |
90 | | -const pathSegments = path.split('/').filter(Boolean) |
91 | | -const breadcrumbSchema = pathSegments.length >= 2 ? { |
92 | | - '@context': 'https://schema.org', |
93 | | - '@type': 'BreadcrumbList', |
94 | | - 'itemListElement': [ |
95 | | - { |
96 | | - '@type': 'ListItem', |
97 | | - 'position': 1, |
98 | | - 'name': 'Home', |
99 | | - 'item': baseUrl, |
100 | | - }, |
101 | | - ...pathSegments.slice(0, -1).map((segment, index) => ({ |
102 | | - '@type': 'ListItem', |
103 | | - 'position': index + 2, |
104 | | - 'name': segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' '), |
105 | | - 'item': absoluteUrl(pathSegments.slice(0, index + 1).join('/'), Astro.site), |
106 | | - })), |
107 | | - ], |
108 | | -} : null |
109 | | -
|
110 | | -// Service schema - for service pages |
111 | | -const serviceSchema = path.startsWith('/services/') && path !== '/services' && path !== '/services/' ? { |
112 | | - '@context': 'https://schema.org', |
113 | | - '@type': 'Service', |
114 | | - 'name': pageTitle, |
115 | | - 'description': description || contactData.company.description, |
116 | | - 'provider': { |
117 | | - '@type': 'Organization', |
118 | | - 'name': contactData.company.name, |
119 | | - 'url': contactData.company.url, |
120 | | - }, |
121 | | - 'url': currentUrl, |
122 | | -} : null |
123 | | -
|
124 | | -// ContactPage schema - for contact page |
125 | | -const contactPageSchema = path === '/contact' || path === '/contact/' ? { |
126 | | - '@context': 'https://schema.org', |
127 | | - '@type': 'ContactPage', |
128 | | - 'name': pageTitle, |
129 | | - 'description': description || contactData.company.description, |
130 | | - 'url': currentUrl, |
131 | | - 'mainEntity': { |
132 | | - '@type': 'Organization', |
133 | | - 'name': contactData.company.name, |
134 | | - 'email': contactData.company.email, |
135 | | - 'url': contactData.company.url, |
136 | | - }, |
137 | | -} : null |
138 | | -
|
139 | | -// Collect all schemas |
140 | | -const schemas = [ |
141 | | - organizationSchema, |
142 | | - webSiteSchema, |
143 | | - articleSchema, |
144 | | - breadcrumbSchema, |
145 | | - serviceSchema, |
146 | | - contactPageSchema, |
147 | | -].filter(Boolean) |
| 16 | +const schemas = getSchemas({ |
| 17 | + astro: Astro, |
| 18 | + path, |
| 19 | + pageTitle, |
| 20 | + ...(description && { description }), |
| 21 | + ...(contentType && { contentType }), |
| 22 | + ...(publishDate && { publishDate }), |
| 23 | + ...(modifiedDate && { modifiedDate }), |
| 24 | + ...(author && { author }), |
| 25 | + ...(image && { image }), |
| 26 | +}) |
148 | 27 | --- |
149 | 28 |
|
150 | | -{schemas.map((schema) => ( |
151 | | - <script is:inline type="application/ld+json" set:html={JSON.stringify(schema)} /> |
| 29 | +{schemas.map(schema => ( |
| 30 | + <script is:inline type="application/ld+json" set:html={schema} /> |
152 | 31 | ))} |
0 commit comments