@zoyth/simple-site-framework 1.0.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/LICENSE +21 -0
- package/README.md +572 -0
- package/bin/create-simple-site.js +390 -0
- package/bin/simple-site.js +664 -0
- package/dist/client.js +135 -0
- package/dist/client.js.map +1 -0
- package/dist/client.mjs +107 -0
- package/dist/client.mjs.map +1 -0
- package/dist/components/index.d.mts +3936 -0
- package/dist/components/index.d.ts +3936 -0
- package/dist/components/index.js +38265 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/index.mjs +38173 -0
- package/dist/components/index.mjs.map +1 -0
- package/dist/config/index.d.mts +298 -0
- package/dist/config/index.d.ts +298 -0
- package/dist/config/index.js +19 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/index.mjs +1 -0
- package/dist/config/index.mjs.map +1 -0
- package/dist/index.d.mts +2184 -0
- package/dist/index.d.ts +2184 -0
- package/dist/index.js +1713 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1605 -0
- package/dist/index.mjs.map +1 -0
- package/dist/lib/i18n/index.js +665 -0
- package/dist/lib/i18n/index.js.map +1 -0
- package/dist/lib/i18n/index.mjs +621 -0
- package/dist/lib/i18n/index.mjs.map +1 -0
- package/docs/DOCUMENTATION-STRUCTURE.md +1156 -0
- package/docs/EXPORTS.md +125 -0
- package/docs/PERFORMANCE.md +757 -0
- package/docs/POLICY-PAGES.md +867 -0
- package/docs/ROADMAP.md +334 -0
- package/docs/SEO.md +455 -0
- package/docs/SITEMAP.md +708 -0
- package/docs/STRUCTURED-DATA.md +671 -0
- package/docs/accessibility/common-patterns.md +529 -0
- package/docs/accessibility/keyboard-navigation.md +263 -0
- package/docs/accessibility/overview.md +122 -0
- package/docs/accessibility/screen-readers.md +311 -0
- package/docs/accessibility/wcag-compliance.md +159 -0
- package/docs/api/README.md +164 -0
- package/docs/api/components/Accessibility.md +356 -0
- package/docs/api/components/Button.md +240 -0
- package/docs/api/components/HeroSection.md +306 -0
- package/docs/architecture/decisions.md +449 -0
- package/docs/components/AnalyticsTracker.md +58 -0
- package/docs/components/AnimatedCounter.md +48 -0
- package/docs/components/AnimatedSection.md +56 -0
- package/docs/components/BlogCard.md +42 -0
- package/docs/components/Checkbox.md +56 -0
- package/docs/components/CodeBlock.md +52 -0
- package/docs/components/ComparisonTable.md +40 -0
- package/docs/components/ComponentDemo.md +38 -0
- package/docs/components/CountdownTimer.md +51 -0
- package/docs/components/ExitIntentModal.md +56 -0
- package/docs/components/FAQAccordion.md +66 -0
- package/docs/components/FeaturesGrid.md +55 -0
- package/docs/components/FileUpload.md +54 -0
- package/docs/components/I18nMetaTags.md +55 -0
- package/docs/components/Icon.md +53 -0
- package/docs/components/LazySection.md +46 -0
- package/docs/components/LiveProof.md +53 -0
- package/docs/components/LoadingSpinner.md +46 -0
- package/docs/components/MultiStepForm.md +48 -0
- package/docs/components/PolicyLayout.md +55 -0
- package/docs/components/PricingTable.md +49 -0
- package/docs/components/Radio.md +59 -0
- package/docs/components/SEOMetaTags.md +58 -0
- package/docs/components/ScriptInjector.md +50 -0
- package/docs/components/Select.md +72 -0
- package/docs/components/Skeleton.md +47 -0
- package/docs/components/StatsSection.md +48 -0
- package/docs/components/StickyBar.md +62 -0
- package/docs/components/StructuredData.md +99 -0
- package/docs/components/StyleGuide.md +46 -0
- package/docs/components/TableOfContents.md +47 -0
- package/docs/components/TestimonialCarousel.md +42 -0
- package/docs/components/Timeline.md +51 -0
- package/docs/components/Toast.md +59 -0
- package/docs/components/TrackedLink.md +62 -0
- package/docs/components/TrustBadges.md +44 -0
- package/docs/components/conversion/MobileCTA.md +363 -0
- package/docs/components/forms/ContactForm.md +75 -0
- package/docs/components/forms/FormField.md +74 -0
- package/docs/components/layout/Footer.md +601 -0
- package/docs/components/layout/Header.md +549 -0
- package/docs/components/layout/LanguageSelector.md +54 -0
- package/docs/components/layout/LanguageSwitcher.md +24 -0
- package/docs/components/overview.md +447 -0
- package/docs/components/sections/AboutSection.md +48 -0
- package/docs/components/sections/CTASection.md +596 -0
- package/docs/components/sections/CaseStudySection.md +47 -0
- package/docs/components/sections/ContactSection.md +599 -0
- package/docs/components/sections/FeatureSection.md +44 -0
- package/docs/components/sections/HeroSection.md +404 -0
- package/docs/components/sections/LogosSection.md +47 -0
- package/docs/components/sections/PersonalTaxesSection.md +23 -0
- package/docs/components/sections/RecruitingSection.md +23 -0
- package/docs/components/sections/SecurePortalSection.md +23 -0
- package/docs/components/sections/ServicePageLayout.md +52 -0
- package/docs/components/sections/ServicesSection.md +49 -0
- package/docs/components/sections/TestimonialSection.md +44 -0
- package/docs/components/sections/WhyChooseUsSection.md +54 -0
- package/docs/components/ui/Breadcrumb.md +70 -0
- package/docs/components/ui/Button.md +514 -0
- package/docs/components/ui/Card.md +501 -0
- package/docs/components/ui/Input.md +54 -0
- package/docs/components/ui/MobileLinks.md +43 -0
- package/docs/components/ui/Modal.md +60 -0
- package/docs/components/ui/Tabs.md +62 -0
- package/docs/components/ui/Textarea.md +52 -0
- package/docs/core-concepts/configuration-driven.md +552 -0
- package/docs/core-concepts/overview.md +351 -0
- package/docs/features/accessibility/README.md +73 -0
- package/docs/features/accessibility/aria-support.md +177 -0
- package/docs/features/accessibility/color-contrast.md +155 -0
- package/docs/features/accessibility/focus-management.md +187 -0
- package/docs/features/accessibility/testing.md +196 -0
- package/docs/features/analytics/README.md +51 -0
- package/docs/features/analytics/ab-testing.md +171 -0
- package/docs/features/analytics/conversion-tracking.md +207 -0
- package/docs/features/analytics/custom-events.md +219 -0
- package/docs/features/analytics/privacy.md +198 -0
- package/docs/features/analytics/setup.md +114 -0
- package/docs/features/analytics/tracking-events.md +224 -0
- package/docs/features/i18n/README.md +51 -0
- package/docs/features/i18n/best-practices.md +273 -0
- package/docs/features/i18n/configuration.md +84 -0
- package/docs/features/i18n/formatting.md +133 -0
- package/docs/features/i18n/locale-detection.md +122 -0
- package/docs/features/i18n/routing.md +99 -0
- package/docs/features/i18n/rtl-support.md +191 -0
- package/docs/features/i18n/translations.md +129 -0
- package/docs/features/internationalization.md +595 -0
- package/docs/features/performance/README.md +77 -0
- package/docs/features/performance/bundle-size.md +134 -0
- package/docs/features/performance/caching.md +131 -0
- package/docs/features/performance/code-splitting.md +121 -0
- package/docs/features/performance/image-optimization.md +110 -0
- package/docs/features/performance/lazy-loading.md +92 -0
- package/docs/features/performance/monitoring.md +148 -0
- package/docs/features/seo/README.md +51 -0
- package/docs/features/seo/best-practices.md +184 -0
- package/docs/features/seo/canonical-urls.md +182 -0
- package/docs/features/seo/meta-tags.md +126 -0
- package/docs/features/seo/open-graph.md +166 -0
- package/docs/features/seo/robots-txt.md +146 -0
- package/docs/features/seo/sitemaps.md +162 -0
- package/docs/features/seo/structured-data.md +166 -0
- package/docs/getting-started/installation.md +292 -0
- package/docs/getting-started/introduction.md +195 -0
- package/docs/getting-started/quick-start.md +460 -0
- package/docs/guides/analytics-setup.md +616 -0
- package/docs/i18n/CONFIGURATION.md +353 -0
- package/docs/i18n/EXAMPLES.md +402 -0
- package/docs/i18n/MIGRATION.md +260 -0
- package/docs/i18n/SEO.md +392 -0
- package/docs/i18n/STATIC-GENERATION-FIX.md +71 -0
- package/docs/migration/changelog.md +136 -0
- package/docs/migration/overview.md +233 -0
- package/docs/recipes/adding-animations.md +475 -0
- package/docs/recipes/forms-with-validation.md +393 -0
- package/package.json +152 -0
|
@@ -0,0 +1,671 @@
|
|
|
1
|
+
# Structured Data (JSON-LD) Guide
|
|
2
|
+
|
|
3
|
+
Complete guide to implementing schema.org structured data with JSON-LD for rich search results.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [Quick Start](#quick-start)
|
|
9
|
+
- [Schema Types](#schema-types)
|
|
10
|
+
- [Helper Functions](#helper-functions)
|
|
11
|
+
- [StructuredData Component](#structureddata-component)
|
|
12
|
+
- [Common Patterns](#common-patterns)
|
|
13
|
+
- [Testing](#testing)
|
|
14
|
+
- [Best Practices](#best-practices)
|
|
15
|
+
|
|
16
|
+
## Overview
|
|
17
|
+
|
|
18
|
+
Structured data helps search engines understand your content better, enabling:
|
|
19
|
+
|
|
20
|
+
- **Rich snippets**: Enhanced search results with ratings, prices, images
|
|
21
|
+
- **Knowledge panels**: Dedicated information boxes in search results
|
|
22
|
+
- **Voice search**: Better answers for voice assistants
|
|
23
|
+
- **Click-through rates**: More attractive search results = more clicks
|
|
24
|
+
|
|
25
|
+
The framework provides:
|
|
26
|
+
- Type-safe schema.org interfaces
|
|
27
|
+
- Helper functions for creating structured data
|
|
28
|
+
- `<StructuredData>` component for rendering JSON-LD
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
### 1. Organization Schema (Recommended for All Sites)
|
|
33
|
+
|
|
34
|
+
Add this to your root layout to tell search engines about your business:
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { StructuredData } from 'simple-site-framework/components';
|
|
38
|
+
import { createOrganization } from 'simple-site-framework/lib/seo/structured-data';
|
|
39
|
+
|
|
40
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
41
|
+
const organization = createOrganization({
|
|
42
|
+
name: 'Acme Inc',
|
|
43
|
+
url: 'https://acme.com',
|
|
44
|
+
logo: 'https://acme.com/logo.png',
|
|
45
|
+
description: 'Leading email marketing platform',
|
|
46
|
+
email: 'hello@acme.com',
|
|
47
|
+
telephone: '+1-555-1234',
|
|
48
|
+
sameAs: [
|
|
49
|
+
'https://twitter.com/acme',
|
|
50
|
+
'https://facebook.com/acme',
|
|
51
|
+
'https://linkedin.com/company/acme'
|
|
52
|
+
],
|
|
53
|
+
address: {
|
|
54
|
+
'@type': 'PostalAddress',
|
|
55
|
+
streetAddress: '123 Main St',
|
|
56
|
+
addressLocality: 'San Francisco',
|
|
57
|
+
addressRegion: 'CA',
|
|
58
|
+
postalCode: '94102',
|
|
59
|
+
addressCountry: 'US'
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<html>
|
|
65
|
+
<head>
|
|
66
|
+
<StructuredData data={organization} />
|
|
67
|
+
</head>
|
|
68
|
+
<body>{children}</body>
|
|
69
|
+
</html>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 2. WebSite Schema with Search
|
|
75
|
+
|
|
76
|
+
Enable Google's sitelinks search box:
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import { createWebSite } from 'simple-site-framework/lib/seo/structured-data';
|
|
80
|
+
|
|
81
|
+
const website = createWebSite({
|
|
82
|
+
name: 'Acme',
|
|
83
|
+
url: 'https://acme.com',
|
|
84
|
+
description: 'Email marketing platform',
|
|
85
|
+
searchUrlTemplate: 'https://acme.com/search?q={search_term_string}'
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
<StructuredData data={website} />
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 3. Multiple Schemas on One Page
|
|
92
|
+
|
|
93
|
+
Combine multiple structured data objects:
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
import { StructuredData } from 'simple-site-framework/components';
|
|
97
|
+
import {
|
|
98
|
+
createOrganization,
|
|
99
|
+
createWebSite,
|
|
100
|
+
createBreadcrumbList
|
|
101
|
+
} from 'simple-site-framework/lib/seo/structured-data';
|
|
102
|
+
|
|
103
|
+
export default function Page() {
|
|
104
|
+
const data = [
|
|
105
|
+
createOrganization({ name: 'Acme', url: 'https://acme.com' }),
|
|
106
|
+
createWebSite({ name: 'Acme', url: 'https://acme.com' }),
|
|
107
|
+
createBreadcrumbList([
|
|
108
|
+
{ name: 'Home', url: 'https://acme.com' },
|
|
109
|
+
{ name: 'Products', url: 'https://acme.com/products' }
|
|
110
|
+
])
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<>
|
|
115
|
+
<StructuredData data={data} />
|
|
116
|
+
<main>...</main>
|
|
117
|
+
</>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Schema Types
|
|
123
|
+
|
|
124
|
+
### Organization
|
|
125
|
+
|
|
126
|
+
Describe your company/organization:
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
import { createOrganization } from 'simple-site-framework/lib/seo/structured-data';
|
|
130
|
+
|
|
131
|
+
const org = createOrganization({
|
|
132
|
+
name: 'Acme Inc',
|
|
133
|
+
url: 'https://acme.com',
|
|
134
|
+
logo: 'https://acme.com/logo.png',
|
|
135
|
+
description: 'Email marketing platform',
|
|
136
|
+
email: 'hello@acme.com',
|
|
137
|
+
telephone: '+1-555-1234',
|
|
138
|
+
|
|
139
|
+
// Address
|
|
140
|
+
address: {
|
|
141
|
+
'@type': 'PostalAddress',
|
|
142
|
+
streetAddress: '123 Main St',
|
|
143
|
+
addressLocality: 'San Francisco',
|
|
144
|
+
addressRegion: 'CA',
|
|
145
|
+
postalCode: '94102',
|
|
146
|
+
addressCountry: 'US'
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
// Social media profiles
|
|
150
|
+
sameAs: [
|
|
151
|
+
'https://twitter.com/acme',
|
|
152
|
+
'https://facebook.com/acme',
|
|
153
|
+
'https://linkedin.com/company/acme',
|
|
154
|
+
'https://github.com/acme'
|
|
155
|
+
],
|
|
156
|
+
|
|
157
|
+
// Contact points
|
|
158
|
+
contactPoint: [
|
|
159
|
+
{
|
|
160
|
+
'@type': 'ContactPoint',
|
|
161
|
+
telephone: '+1-555-SALES',
|
|
162
|
+
email: 'sales@acme.com',
|
|
163
|
+
contactType: 'sales',
|
|
164
|
+
availableLanguage: ['English', 'French'],
|
|
165
|
+
areaServed: ['US', 'CA']
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
'@type': 'ContactPoint',
|
|
169
|
+
telephone: '+1-555-SUPPORT',
|
|
170
|
+
email: 'support@acme.com',
|
|
171
|
+
contactType: 'customer service',
|
|
172
|
+
availableLanguage: ['English', 'French', 'Spanish']
|
|
173
|
+
}
|
|
174
|
+
],
|
|
175
|
+
|
|
176
|
+
// Founders
|
|
177
|
+
founders: [
|
|
178
|
+
{
|
|
179
|
+
'@type': 'Person',
|
|
180
|
+
name: 'Jane Doe',
|
|
181
|
+
jobTitle: 'CEO',
|
|
182
|
+
image: 'https://acme.com/team/jane.jpg'
|
|
183
|
+
}
|
|
184
|
+
],
|
|
185
|
+
|
|
186
|
+
foundingDate: '2020-01-15'
|
|
187
|
+
});
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### WebSite
|
|
191
|
+
|
|
192
|
+
Define your website with optional search functionality:
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
import { createWebSite } from 'simple-site-framework/lib/seo/structured-data';
|
|
196
|
+
|
|
197
|
+
const website = createWebSite({
|
|
198
|
+
name: 'Acme',
|
|
199
|
+
url: 'https://acme.com',
|
|
200
|
+
description: 'The best email marketing platform',
|
|
201
|
+
|
|
202
|
+
// Enable Google's sitelinks search box
|
|
203
|
+
searchUrlTemplate: 'https://acme.com/search?q={search_term_string}',
|
|
204
|
+
|
|
205
|
+
publisher: createOrganization({
|
|
206
|
+
name: 'Acme Inc',
|
|
207
|
+
url: 'https://acme.com'
|
|
208
|
+
})
|
|
209
|
+
});
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Product
|
|
213
|
+
|
|
214
|
+
Product pages for e-commerce or SaaS plans:
|
|
215
|
+
|
|
216
|
+
```tsx
|
|
217
|
+
import { createProduct } from 'simple-site-framework/lib/seo/structured-data';
|
|
218
|
+
|
|
219
|
+
const product = createProduct({
|
|
220
|
+
name: 'Premium Email Plan',
|
|
221
|
+
description: 'Unlimited emails, advanced automation, priority support',
|
|
222
|
+
image: [
|
|
223
|
+
'https://acme.com/images/premium-1.jpg',
|
|
224
|
+
'https://acme.com/images/premium-2.jpg'
|
|
225
|
+
],
|
|
226
|
+
|
|
227
|
+
brand: createOrganization({
|
|
228
|
+
name: 'Acme',
|
|
229
|
+
url: 'https://acme.com'
|
|
230
|
+
}),
|
|
231
|
+
|
|
232
|
+
offers: {
|
|
233
|
+
'@type': 'Offer',
|
|
234
|
+
price: '99.00',
|
|
235
|
+
priceCurrency: 'USD',
|
|
236
|
+
priceValidUntil: '2024-12-31',
|
|
237
|
+
availability: 'InStock',
|
|
238
|
+
url: 'https://acme.com/pricing/premium',
|
|
239
|
+
seller: createOrganization({
|
|
240
|
+
name: 'Acme Inc',
|
|
241
|
+
url: 'https://acme.com'
|
|
242
|
+
})
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
aggregateRating: {
|
|
246
|
+
'@type': 'AggregateRating',
|
|
247
|
+
ratingValue: 4.8,
|
|
248
|
+
reviewCount: 127,
|
|
249
|
+
bestRating: 5,
|
|
250
|
+
worstRating: 1
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
review: [
|
|
254
|
+
{
|
|
255
|
+
'@type': 'Review',
|
|
256
|
+
author: {
|
|
257
|
+
'@type': 'Person',
|
|
258
|
+
name: 'John Smith'
|
|
259
|
+
},
|
|
260
|
+
datePublished: '2024-01-15',
|
|
261
|
+
reviewBody: 'Excellent email platform! Easy to use and great support.',
|
|
262
|
+
reviewRating: {
|
|
263
|
+
'@type': 'Rating',
|
|
264
|
+
ratingValue: 5,
|
|
265
|
+
bestRating: 5
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
]
|
|
269
|
+
});
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### FAQ Page
|
|
273
|
+
|
|
274
|
+
FAQ pages with questions and answers:
|
|
275
|
+
|
|
276
|
+
```tsx
|
|
277
|
+
import { createFAQPage } from 'simple-site-framework/lib/seo/structured-data';
|
|
278
|
+
|
|
279
|
+
const faq = createFAQPage([
|
|
280
|
+
{
|
|
281
|
+
question: 'What is your refund policy?',
|
|
282
|
+
answer: 'We offer a 30-day money-back guarantee on all plans. No questions asked.'
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
question: 'Do you offer customer support?',
|
|
286
|
+
answer: 'Yes! We provide 24/7 email and chat support to all customers, plus phone support for Premium plans.'
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
question: 'Can I cancel anytime?',
|
|
290
|
+
answer: 'Absolutely. Cancel your subscription anytime from your account dashboard. No cancellation fees.'
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
question: 'Is my data secure?',
|
|
294
|
+
answer: 'Yes. We use bank-level encryption (AES-256) and are SOC 2 Type II certified.'
|
|
295
|
+
}
|
|
296
|
+
]);
|
|
297
|
+
|
|
298
|
+
<StructuredData data={faq} />
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Article / Blog Post
|
|
302
|
+
|
|
303
|
+
Blog posts and articles:
|
|
304
|
+
|
|
305
|
+
```tsx
|
|
306
|
+
import { createArticle } from 'simple-site-framework/lib/seo/structured-data';
|
|
307
|
+
|
|
308
|
+
const article = createArticle({
|
|
309
|
+
type: 'BlogPosting', // or 'Article' or 'NewsArticle'
|
|
310
|
+
headline: '10 Email Marketing Tips to Triple Your ROI',
|
|
311
|
+
description: 'Learn proven strategies to boost your email marketing performance',
|
|
312
|
+
image: [
|
|
313
|
+
'https://acme.com/blog/email-tips-hero.jpg',
|
|
314
|
+
'https://acme.com/blog/email-tips-square.jpg'
|
|
315
|
+
],
|
|
316
|
+
|
|
317
|
+
author: {
|
|
318
|
+
'@type': 'Person',
|
|
319
|
+
name: 'Jane Doe',
|
|
320
|
+
url: 'https://acme.com/authors/jane-doe',
|
|
321
|
+
image: 'https://acme.com/authors/jane.jpg',
|
|
322
|
+
jobTitle: 'Head of Marketing'
|
|
323
|
+
},
|
|
324
|
+
|
|
325
|
+
publisher: createOrganization({
|
|
326
|
+
name: 'Acme Blog',
|
|
327
|
+
url: 'https://acme.com/blog',
|
|
328
|
+
logo: 'https://acme.com/logo.png'
|
|
329
|
+
}),
|
|
330
|
+
|
|
331
|
+
datePublished: '2024-01-15T10:00:00Z',
|
|
332
|
+
dateModified: '2024-01-20T14:30:00Z',
|
|
333
|
+
mainEntityOfPage: 'https://acme.com/blog/email-marketing-tips'
|
|
334
|
+
});
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### Breadcrumbs
|
|
338
|
+
|
|
339
|
+
Navigation breadcrumbs for better site structure:
|
|
340
|
+
|
|
341
|
+
```tsx
|
|
342
|
+
import { createBreadcrumbList } from 'simple-site-framework/lib/seo/structured-data';
|
|
343
|
+
|
|
344
|
+
const breadcrumbs = createBreadcrumbList([
|
|
345
|
+
{ name: 'Home', url: 'https://acme.com' },
|
|
346
|
+
{ name: 'Blog', url: 'https://acme.com/blog' },
|
|
347
|
+
{ name: 'Email Marketing', url: 'https://acme.com/blog/category/email-marketing' },
|
|
348
|
+
{ name: '10 Email Tips' } // Current page (no URL)
|
|
349
|
+
]);
|
|
350
|
+
|
|
351
|
+
<StructuredData data={breadcrumbs} />
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Helper Functions
|
|
355
|
+
|
|
356
|
+
### createOrganization()
|
|
357
|
+
|
|
358
|
+
```tsx
|
|
359
|
+
function createOrganization(data: Omit<Organization, '@type'>): Organization
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### createWebSite()
|
|
363
|
+
|
|
364
|
+
```tsx
|
|
365
|
+
function createWebSite(data: {
|
|
366
|
+
name: string;
|
|
367
|
+
url: string;
|
|
368
|
+
description?: string;
|
|
369
|
+
publisher?: Organization;
|
|
370
|
+
searchUrlTemplate?: string;
|
|
371
|
+
}): WebSite
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### createProduct()
|
|
375
|
+
|
|
376
|
+
```tsx
|
|
377
|
+
function createProduct(data: Omit<Product, '@type'>): Product
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### createFAQPage()
|
|
381
|
+
|
|
382
|
+
```tsx
|
|
383
|
+
function createFAQPage(
|
|
384
|
+
faqs: Array<{ question: string; answer: string }>
|
|
385
|
+
): FAQPage
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### createArticle()
|
|
389
|
+
|
|
390
|
+
```tsx
|
|
391
|
+
function createArticle(data: {
|
|
392
|
+
headline: string;
|
|
393
|
+
description?: string;
|
|
394
|
+
image?: string | string[];
|
|
395
|
+
author: Person | string;
|
|
396
|
+
publisher: Organization;
|
|
397
|
+
datePublished: string;
|
|
398
|
+
dateModified?: string;
|
|
399
|
+
mainEntityOfPage?: string;
|
|
400
|
+
type?: 'Article' | 'BlogPosting' | 'NewsArticle';
|
|
401
|
+
}): Article
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### createBreadcrumbList()
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
function createBreadcrumbList(
|
|
408
|
+
items: Array<{ name: string; url?: string }>
|
|
409
|
+
): BreadcrumbList
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### serializeStructuredData()
|
|
413
|
+
|
|
414
|
+
Manually serialize structured data to JSON-LD string:
|
|
415
|
+
|
|
416
|
+
```tsx
|
|
417
|
+
function serializeStructuredData(data: Thing | Thing[]): string
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## StructuredData Component
|
|
421
|
+
|
|
422
|
+
```tsx
|
|
423
|
+
import { StructuredData } from 'simple-site-framework/components';
|
|
424
|
+
|
|
425
|
+
interface StructuredDataProps {
|
|
426
|
+
/** Structured data object or array of objects */
|
|
427
|
+
data: Thing | Thing[];
|
|
428
|
+
}
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
The component renders a `<script type="application/ld+json">` tag with your structured data.
|
|
432
|
+
|
|
433
|
+
## Common Patterns
|
|
434
|
+
|
|
435
|
+
### Homepage
|
|
436
|
+
|
|
437
|
+
```tsx
|
|
438
|
+
import { StructuredData } from 'simple-site-framework/components';
|
|
439
|
+
import { createOrganization, createWebSite } from 'simple-site-framework/lib/seo/structured-data';
|
|
440
|
+
|
|
441
|
+
export default function HomePage() {
|
|
442
|
+
const data = [
|
|
443
|
+
createOrganization({
|
|
444
|
+
name: 'Acme Inc',
|
|
445
|
+
url: 'https://acme.com',
|
|
446
|
+
logo: 'https://acme.com/logo.png',
|
|
447
|
+
sameAs: ['https://twitter.com/acme']
|
|
448
|
+
}),
|
|
449
|
+
createWebSite({
|
|
450
|
+
name: 'Acme',
|
|
451
|
+
url: 'https://acme.com',
|
|
452
|
+
searchUrlTemplate: 'https://acme.com/search?q={search_term_string}'
|
|
453
|
+
})
|
|
454
|
+
];
|
|
455
|
+
|
|
456
|
+
return (
|
|
457
|
+
<>
|
|
458
|
+
<StructuredData data={data} />
|
|
459
|
+
<main>...</main>
|
|
460
|
+
</>
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### Product/Pricing Page
|
|
466
|
+
|
|
467
|
+
```tsx
|
|
468
|
+
const product = createProduct({
|
|
469
|
+
name: 'Premium Plan',
|
|
470
|
+
description: 'Full-featured email marketing',
|
|
471
|
+
offers: {
|
|
472
|
+
'@type': 'Offer',
|
|
473
|
+
price: '99.00',
|
|
474
|
+
priceCurrency: 'USD',
|
|
475
|
+
availability: 'InStock'
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const breadcrumbs = createBreadcrumbList([
|
|
480
|
+
{ name: 'Home', url: 'https://acme.com' },
|
|
481
|
+
{ name: 'Pricing', url: 'https://acme.com/pricing' },
|
|
482
|
+
{ name: 'Premium Plan' }
|
|
483
|
+
]);
|
|
484
|
+
|
|
485
|
+
<StructuredData data={[product, breadcrumbs]} />
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Blog Post
|
|
489
|
+
|
|
490
|
+
```tsx
|
|
491
|
+
const article = createArticle({
|
|
492
|
+
headline: 'Blog Post Title',
|
|
493
|
+
author: { '@type': 'Person', name: 'Jane Doe' },
|
|
494
|
+
publisher: createOrganization({ name: 'Acme Blog' }),
|
|
495
|
+
datePublished: '2024-01-15T10:00:00Z',
|
|
496
|
+
image: 'https://acme.com/blog/image.jpg'
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
const breadcrumbs = createBreadcrumbList([
|
|
500
|
+
{ name: 'Home', url: 'https://acme.com' },
|
|
501
|
+
{ name: 'Blog', url: 'https://acme.com/blog' },
|
|
502
|
+
{ name: 'Post Title' }
|
|
503
|
+
]);
|
|
504
|
+
|
|
505
|
+
<StructuredData data={[article, breadcrumbs]} />
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### FAQ Page
|
|
509
|
+
|
|
510
|
+
```tsx
|
|
511
|
+
const faq = createFAQPage([
|
|
512
|
+
{ question: 'Q1?', answer: 'A1' },
|
|
513
|
+
{ question: 'Q2?', answer: 'A2' }
|
|
514
|
+
]);
|
|
515
|
+
|
|
516
|
+
<StructuredData data={faq} />
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
## Testing
|
|
520
|
+
|
|
521
|
+
### Google's Rich Results Test
|
|
522
|
+
|
|
523
|
+
1. Go to: https://search.google.com/test/rich-results
|
|
524
|
+
2. Enter your URL or paste HTML
|
|
525
|
+
3. Check for errors and warnings
|
|
526
|
+
4. Preview how it appears in search
|
|
527
|
+
|
|
528
|
+
### Schema.org Validator
|
|
529
|
+
|
|
530
|
+
1. Go to: https://validator.schema.org/
|
|
531
|
+
2. Paste your JSON-LD
|
|
532
|
+
3. Verify structure matches schema.org spec
|
|
533
|
+
|
|
534
|
+
### Manual Inspection
|
|
535
|
+
|
|
536
|
+
```tsx
|
|
537
|
+
// View rendered JSON-LD in browser DevTools
|
|
538
|
+
document.querySelector('script[type="application/ld+json"]').textContent
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
## Best Practices
|
|
542
|
+
|
|
543
|
+
### 1. Use Absolute URLs
|
|
544
|
+
|
|
545
|
+
Always use absolute URLs (https://...) never relative (/page):
|
|
546
|
+
|
|
547
|
+
```tsx
|
|
548
|
+
// ✅ Good
|
|
549
|
+
image: 'https://acme.com/image.jpg'
|
|
550
|
+
url: 'https://acme.com/page'
|
|
551
|
+
|
|
552
|
+
// ❌ Bad
|
|
553
|
+
image: '/image.jpg'
|
|
554
|
+
url: '/page'
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### 2. Match Visible Content
|
|
558
|
+
|
|
559
|
+
Structured data should match what users see on the page:
|
|
560
|
+
|
|
561
|
+
```tsx
|
|
562
|
+
// ✅ Good - Matches H1 on page
|
|
563
|
+
headline: '10 Email Marketing Tips'
|
|
564
|
+
|
|
565
|
+
// ❌ Bad - Different from H1
|
|
566
|
+
headline: 'Amazing Email Tips You Won\'t Believe!'
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### 3. Use ISO 8601 Dates
|
|
570
|
+
|
|
571
|
+
```tsx
|
|
572
|
+
// ✅ Good
|
|
573
|
+
datePublished: '2024-01-15T10:00:00Z'
|
|
574
|
+
datePublished: '2024-01-15'
|
|
575
|
+
|
|
576
|
+
// ❌ Bad
|
|
577
|
+
datePublished: 'January 15, 2024'
|
|
578
|
+
datePublished: '01/15/2024'
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### 4. Multiple Images
|
|
582
|
+
|
|
583
|
+
Provide multiple image sizes for better rich results:
|
|
584
|
+
|
|
585
|
+
```tsx
|
|
586
|
+
image: [
|
|
587
|
+
'https://acme.com/image-1200x630.jpg', // OG image (1.91:1)
|
|
588
|
+
'https://acme.com/image-800x800.jpg', // Square (1:1)
|
|
589
|
+
'https://acme.com/image-1600x900.jpg' // 16:9
|
|
590
|
+
]
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### 5. Complete Publisher Info
|
|
594
|
+
|
|
595
|
+
Articles require complete publisher with logo:
|
|
596
|
+
|
|
597
|
+
```tsx
|
|
598
|
+
publisher: {
|
|
599
|
+
'@type': 'Organization',
|
|
600
|
+
name: 'Acme Blog',
|
|
601
|
+
logo: 'https://acme.com/logo.png' // Required
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### 6. Rating Values
|
|
606
|
+
|
|
607
|
+
Use numeric values with proper ranges:
|
|
608
|
+
|
|
609
|
+
```tsx
|
|
610
|
+
// ✅ Good
|
|
611
|
+
aggregateRating: {
|
|
612
|
+
'@type': 'AggregateRating',
|
|
613
|
+
ratingValue: 4.8,
|
|
614
|
+
bestRating: 5,
|
|
615
|
+
worstRating: 1,
|
|
616
|
+
reviewCount: 127
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// ❌ Bad - Missing context
|
|
620
|
+
aggregateRating: {
|
|
621
|
+
'@type': 'AggregateRating',
|
|
622
|
+
ratingValue: 4.8
|
|
623
|
+
}
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
### 7. Avoid Spam
|
|
627
|
+
|
|
628
|
+
Don't add structured data for content not on the page:
|
|
629
|
+
|
|
630
|
+
```tsx
|
|
631
|
+
// ❌ Bad - Review not visible on page
|
|
632
|
+
review: [{
|
|
633
|
+
author: 'Fake Reviewer',
|
|
634
|
+
reviewBody: 'Amazing product!',
|
|
635
|
+
reviewRating: { ratingValue: 5 }
|
|
636
|
+
}]
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### 8. Test Before Deploy
|
|
640
|
+
|
|
641
|
+
Always test with Google's Rich Results Test before deploying.
|
|
642
|
+
|
|
643
|
+
### 9. Monitor Search Console
|
|
644
|
+
|
|
645
|
+
Check Google Search Console → Enhancements for errors and warnings.
|
|
646
|
+
|
|
647
|
+
### 10. Keep It Updated
|
|
648
|
+
|
|
649
|
+
Update `dateModified` when content changes significantly.
|
|
650
|
+
|
|
651
|
+
## Checklist
|
|
652
|
+
|
|
653
|
+
- [ ] Organization schema on all pages
|
|
654
|
+
- [ ] WebSite schema with search action (homepage)
|
|
655
|
+
- [ ] Product schema on product/pricing pages
|
|
656
|
+
- [ ] Article schema on blog posts
|
|
657
|
+
- [ ] FAQ schema on FAQ pages
|
|
658
|
+
- [ ] Breadcrumbs on deep pages
|
|
659
|
+
- [ ] All URLs are absolute (https://)
|
|
660
|
+
- [ ] Dates in ISO 8601 format
|
|
661
|
+
- [ ] Images are high quality (min 1200px wide)
|
|
662
|
+
- [ ] Tested with Google Rich Results Test
|
|
663
|
+
- [ ] No errors in Search Console
|
|
664
|
+
|
|
665
|
+
## Resources
|
|
666
|
+
|
|
667
|
+
- [Schema.org Documentation](https://schema.org/)
|
|
668
|
+
- [Google Search Central - Structured Data](https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data)
|
|
669
|
+
- [Google Rich Results Test](https://search.google.com/test/rich-results)
|
|
670
|
+
- [Schema.org Validator](https://validator.schema.org/)
|
|
671
|
+
- [JSON-LD Playground](https://json-ld.org/playground/)
|