@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,166 @@
|
|
|
1
|
+
# Open Graph
|
|
2
|
+
|
|
3
|
+
Social media preview cards for Facebook, Twitter, LinkedIn, and more.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Open Graph tags control how your pages appear when shared on social media. The framework integrates OG tags into the SEOMetaTags component.
|
|
8
|
+
|
|
9
|
+
## Basic Setup
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { SEOMetaTags } from '@zoyth/simple-site-framework/components';
|
|
13
|
+
|
|
14
|
+
<SEOMetaTags
|
|
15
|
+
title="My Page Title"
|
|
16
|
+
description="Page description"
|
|
17
|
+
openGraph={{
|
|
18
|
+
type: 'website',
|
|
19
|
+
image: 'https://example.com/og-image.jpg',
|
|
20
|
+
url: 'https://example.com/page',
|
|
21
|
+
siteName: 'My Company',
|
|
22
|
+
}}
|
|
23
|
+
/>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Open Graph Properties
|
|
27
|
+
|
|
28
|
+
| Property | Type | Description |
|
|
29
|
+
|----------|------|-------------|
|
|
30
|
+
| `type` | `string` | Content type: 'website', 'article', 'product' |
|
|
31
|
+
| `image` | `string` | Preview image URL (1200x630px recommended) |
|
|
32
|
+
| `url` | `string` | Canonical URL of the page |
|
|
33
|
+
| `siteName` | `string` | Site name |
|
|
34
|
+
| `locale` | `string` | Content locale (e.g., 'en_US') |
|
|
35
|
+
| `title` | `string` | Override title (defaults to page title) |
|
|
36
|
+
| `description` | `string` | Override description |
|
|
37
|
+
|
|
38
|
+
## Twitter Cards
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
<SEOMetaTags
|
|
42
|
+
title="My Page"
|
|
43
|
+
description="Description"
|
|
44
|
+
twitter={{
|
|
45
|
+
card: 'summary_large_image',
|
|
46
|
+
site: '@mycompany',
|
|
47
|
+
creator: '@author',
|
|
48
|
+
image: 'https://example.com/twitter-card.jpg',
|
|
49
|
+
}}
|
|
50
|
+
/>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Card Types
|
|
54
|
+
|
|
55
|
+
| Type | Description |
|
|
56
|
+
|------|-------------|
|
|
57
|
+
| `summary` | Small square image with title and description |
|
|
58
|
+
| `summary_large_image` | Large image with title and description |
|
|
59
|
+
|
|
60
|
+
## Image Guidelines
|
|
61
|
+
|
|
62
|
+
### Open Graph Images
|
|
63
|
+
|
|
64
|
+
- **Size:** 1200 x 630 pixels
|
|
65
|
+
- **Format:** JPEG or PNG
|
|
66
|
+
- **Max size:** 5MB (Facebook), 5MB (LinkedIn)
|
|
67
|
+
- **Aspect ratio:** 1.91:1
|
|
68
|
+
|
|
69
|
+
### Twitter Images
|
|
70
|
+
|
|
71
|
+
- **summary:** 144 x 144 pixels minimum
|
|
72
|
+
- **summary_large_image:** 300 x 157 pixels minimum
|
|
73
|
+
- **Max size:** 5MB
|
|
74
|
+
- **Format:** JPEG, PNG, GIF
|
|
75
|
+
|
|
76
|
+
## Per-Page Configuration
|
|
77
|
+
|
|
78
|
+
### Homepage
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
<SEOMetaTags
|
|
82
|
+
title="My Company - Professional Services"
|
|
83
|
+
description="Leading provider of professional services"
|
|
84
|
+
openGraph={{
|
|
85
|
+
type: 'website',
|
|
86
|
+
image: '/images/og-home.jpg',
|
|
87
|
+
siteName: 'My Company',
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Blog Post
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
<SEOMetaTags
|
|
96
|
+
title={post.title}
|
|
97
|
+
description={post.excerpt}
|
|
98
|
+
openGraph={{
|
|
99
|
+
type: 'article',
|
|
100
|
+
image: post.coverImage,
|
|
101
|
+
article: {
|
|
102
|
+
publishedTime: post.publishedAt,
|
|
103
|
+
author: post.author.name,
|
|
104
|
+
tags: post.tags,
|
|
105
|
+
},
|
|
106
|
+
}}
|
|
107
|
+
/>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Service Page
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
<SEOMetaTags
|
|
114
|
+
title="Tax Preparation Services"
|
|
115
|
+
description="Professional tax services"
|
|
116
|
+
openGraph={{
|
|
117
|
+
type: 'website',
|
|
118
|
+
image: '/images/og-tax-services.jpg',
|
|
119
|
+
}}
|
|
120
|
+
/>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Multi-Language OG Tags
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
<SEOMetaTags
|
|
127
|
+
title={getLocalizedString(title, locale)}
|
|
128
|
+
description={getLocalizedString(description, locale)}
|
|
129
|
+
openGraph={{
|
|
130
|
+
type: 'website',
|
|
131
|
+
locale: locale === 'fr' ? 'fr_CA' : 'en_US',
|
|
132
|
+
image: `/images/og-${locale}.jpg`,
|
|
133
|
+
}}
|
|
134
|
+
/>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Testing
|
|
138
|
+
|
|
139
|
+
### Facebook Sharing Debugger
|
|
140
|
+
|
|
141
|
+
Test how your page appears on Facebook:
|
|
142
|
+
- [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/)
|
|
143
|
+
|
|
144
|
+
### Twitter Card Validator
|
|
145
|
+
|
|
146
|
+
Test Twitter card rendering:
|
|
147
|
+
- [Twitter Card Validator](https://cards-dev.twitter.com/validator)
|
|
148
|
+
|
|
149
|
+
### LinkedIn Post Inspector
|
|
150
|
+
|
|
151
|
+
Test LinkedIn previews:
|
|
152
|
+
- [LinkedIn Post Inspector](https://www.linkedin.com/post-inspector/)
|
|
153
|
+
|
|
154
|
+
## Best Practices
|
|
155
|
+
|
|
156
|
+
- Always include an og:image - posts with images get more engagement
|
|
157
|
+
- Use high-quality images at recommended dimensions
|
|
158
|
+
- Test on each platform before launch
|
|
159
|
+
- Set unique OG data per page
|
|
160
|
+
- Keep titles under 60 characters
|
|
161
|
+
- Keep descriptions under 155 characters
|
|
162
|
+
|
|
163
|
+
## See Also
|
|
164
|
+
|
|
165
|
+
- [Meta Tags](./meta-tags.md)
|
|
166
|
+
- [SEOMetaTags Component](../../components/SEOMetaTags.md)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Robots.txt
|
|
2
|
+
|
|
3
|
+
Control search engine crawling behavior.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `robots.txt` file tells search engine crawlers which pages to crawl and which to skip.
|
|
8
|
+
|
|
9
|
+
## Next.js Configuration
|
|
10
|
+
|
|
11
|
+
Use the Next.js App Router convention:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// app/robots.ts
|
|
15
|
+
import type { MetadataRoute } from 'next';
|
|
16
|
+
|
|
17
|
+
export default function robots(): MetadataRoute.Robots {
|
|
18
|
+
return {
|
|
19
|
+
rules: {
|
|
20
|
+
userAgent: '*',
|
|
21
|
+
allow: '/',
|
|
22
|
+
disallow: ['/api/', '/admin/', '/_next/'],
|
|
23
|
+
},
|
|
24
|
+
sitemap: 'https://example.com/sitemap.xml',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Common Configurations
|
|
30
|
+
|
|
31
|
+
### Allow All (Default)
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
export default function robots(): MetadataRoute.Robots {
|
|
35
|
+
return {
|
|
36
|
+
rules: {
|
|
37
|
+
userAgent: '*',
|
|
38
|
+
allow: '/',
|
|
39
|
+
},
|
|
40
|
+
sitemap: 'https://example.com/sitemap.xml',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Block Specific Paths
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
export default function robots(): MetadataRoute.Robots {
|
|
49
|
+
return {
|
|
50
|
+
rules: {
|
|
51
|
+
userAgent: '*',
|
|
52
|
+
allow: '/',
|
|
53
|
+
disallow: [
|
|
54
|
+
'/api/',
|
|
55
|
+
'/admin/',
|
|
56
|
+
'/private/',
|
|
57
|
+
'/draft/',
|
|
58
|
+
'/thank-you', // Post-conversion pages
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
sitemap: 'https://example.com/sitemap.xml',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Different Rules Per Bot
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
export default function robots(): MetadataRoute.Robots {
|
|
70
|
+
return {
|
|
71
|
+
rules: [
|
|
72
|
+
{
|
|
73
|
+
userAgent: 'Googlebot',
|
|
74
|
+
allow: '/',
|
|
75
|
+
disallow: '/admin/',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
userAgent: 'Bingbot',
|
|
79
|
+
allow: '/',
|
|
80
|
+
disallow: '/admin/',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
userAgent: '*',
|
|
84
|
+
allow: '/',
|
|
85
|
+
disallow: ['/api/', '/admin/'],
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
sitemap: 'https://example.com/sitemap.xml',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Staging Environment
|
|
94
|
+
|
|
95
|
+
Block all crawling on staging:
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
export default function robots(): MetadataRoute.Robots {
|
|
99
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
100
|
+
return {
|
|
101
|
+
rules: {
|
|
102
|
+
userAgent: '*',
|
|
103
|
+
disallow: '/',
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
rules: {
|
|
110
|
+
userAgent: '*',
|
|
111
|
+
allow: '/',
|
|
112
|
+
},
|
|
113
|
+
sitemap: 'https://example.com/sitemap.xml',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Crawl Delay
|
|
119
|
+
|
|
120
|
+
Add crawl delay for polite crawling:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
rules: {
|
|
124
|
+
userAgent: '*',
|
|
125
|
+
allow: '/',
|
|
126
|
+
crawlDelay: 10, // seconds between requests
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Verification
|
|
131
|
+
|
|
132
|
+
Visit `http://localhost:3000/robots.txt` to check output.
|
|
133
|
+
|
|
134
|
+
## Best Practices
|
|
135
|
+
|
|
136
|
+
- Always include sitemap reference
|
|
137
|
+
- Block API routes and admin pages
|
|
138
|
+
- Block staging/preview environments entirely
|
|
139
|
+
- Don't block CSS/JS files (search engines need them for rendering)
|
|
140
|
+
- Use noindex meta tag for pages that should appear in sitemap but not search
|
|
141
|
+
- Test with Google Search Console
|
|
142
|
+
|
|
143
|
+
## See Also
|
|
144
|
+
|
|
145
|
+
- [Sitemaps](./sitemaps.md)
|
|
146
|
+
- [Meta Tags](./meta-tags.md)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Sitemaps
|
|
2
|
+
|
|
3
|
+
XML sitemap generation for search engine discovery.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Sitemaps help search engines discover and index all pages on your site. The framework supports sitemap generation for multi-language sites.
|
|
8
|
+
|
|
9
|
+
## Next.js Sitemap
|
|
10
|
+
|
|
11
|
+
Use the Next.js App Router sitemap convention:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// app/sitemap.ts
|
|
15
|
+
import type { MetadataRoute } from 'next';
|
|
16
|
+
|
|
17
|
+
export default function sitemap(): MetadataRoute.Sitemap {
|
|
18
|
+
const baseUrl = 'https://example.com';
|
|
19
|
+
|
|
20
|
+
return [
|
|
21
|
+
{
|
|
22
|
+
url: baseUrl,
|
|
23
|
+
lastModified: new Date(),
|
|
24
|
+
changeFrequency: 'weekly',
|
|
25
|
+
priority: 1,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
url: `${baseUrl}/about`,
|
|
29
|
+
lastModified: new Date(),
|
|
30
|
+
changeFrequency: 'monthly',
|
|
31
|
+
priority: 0.8,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
url: `${baseUrl}/services`,
|
|
35
|
+
lastModified: new Date(),
|
|
36
|
+
changeFrequency: 'monthly',
|
|
37
|
+
priority: 0.8,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
url: `${baseUrl}/contact`,
|
|
41
|
+
lastModified: new Date(),
|
|
42
|
+
changeFrequency: 'yearly',
|
|
43
|
+
priority: 0.5,
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Multi-Language Sitemaps
|
|
50
|
+
|
|
51
|
+
Include locale alternates:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// app/sitemap.ts
|
|
55
|
+
import type { MetadataRoute } from 'next';
|
|
56
|
+
|
|
57
|
+
export default function sitemap(): MetadataRoute.Sitemap {
|
|
58
|
+
const baseUrl = 'https://example.com';
|
|
59
|
+
const locales = ['en', 'fr', 'es'];
|
|
60
|
+
const pages = ['', '/about', '/services', '/contact'];
|
|
61
|
+
|
|
62
|
+
return pages.flatMap(page =>
|
|
63
|
+
locales.map(locale => ({
|
|
64
|
+
url: `${baseUrl}${locale === 'en' ? '' : `/${locale}`}${page}`,
|
|
65
|
+
lastModified: new Date(),
|
|
66
|
+
alternates: {
|
|
67
|
+
languages: Object.fromEntries(
|
|
68
|
+
locales.map(l => [
|
|
69
|
+
l,
|
|
70
|
+
`${baseUrl}${l === 'en' ? '' : `/${l}`}${page}`,
|
|
71
|
+
])
|
|
72
|
+
),
|
|
73
|
+
},
|
|
74
|
+
}))
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Dynamic Sitemaps
|
|
80
|
+
|
|
81
|
+
Generate from data sources:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// app/sitemap.ts
|
|
85
|
+
export default async function sitemap(): MetadataRoute.Sitemap {
|
|
86
|
+
const baseUrl = 'https://example.com';
|
|
87
|
+
|
|
88
|
+
// Fetch dynamic pages
|
|
89
|
+
const posts = await getBlogPosts();
|
|
90
|
+
const services = await getServices();
|
|
91
|
+
|
|
92
|
+
const staticPages = [
|
|
93
|
+
{ url: baseUrl, priority: 1 },
|
|
94
|
+
{ url: `${baseUrl}/about`, priority: 0.8 },
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
const blogPages = posts.map(post => ({
|
|
98
|
+
url: `${baseUrl}/blog/${post.slug}`,
|
|
99
|
+
lastModified: post.updatedAt,
|
|
100
|
+
priority: 0.6,
|
|
101
|
+
}));
|
|
102
|
+
|
|
103
|
+
const servicePages = services.map(service => ({
|
|
104
|
+
url: `${baseUrl}/services/${service.slug}`,
|
|
105
|
+
lastModified: service.updatedAt,
|
|
106
|
+
priority: 0.8,
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
return [...staticPages, ...blogPages, ...servicePages];
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Sitemap Index
|
|
114
|
+
|
|
115
|
+
For large sites, split into multiple sitemaps:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// app/sitemap.ts
|
|
119
|
+
export async function generateSitemaps() {
|
|
120
|
+
const totalPages = await getPageCount();
|
|
121
|
+
const sitemapsNeeded = Math.ceil(totalPages / 50000);
|
|
122
|
+
|
|
123
|
+
return Array.from({ length: sitemapsNeeded }, (_, i) => ({ id: i }));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export default async function sitemap({ id }: { id: number }) {
|
|
127
|
+
const start = id * 50000;
|
|
128
|
+
const pages = await getPages(start, 50000);
|
|
129
|
+
|
|
130
|
+
return pages.map(page => ({
|
|
131
|
+
url: `https://example.com${page.path}`,
|
|
132
|
+
lastModified: page.updatedAt,
|
|
133
|
+
}));
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Verification
|
|
138
|
+
|
|
139
|
+
### Google Search Console
|
|
140
|
+
|
|
141
|
+
1. Submit sitemap URL in Search Console
|
|
142
|
+
2. Monitor indexing status
|
|
143
|
+
3. Check for errors
|
|
144
|
+
|
|
145
|
+
### Test Locally
|
|
146
|
+
|
|
147
|
+
Visit `http://localhost:3000/sitemap.xml` to verify output.
|
|
148
|
+
|
|
149
|
+
## Best Practices
|
|
150
|
+
|
|
151
|
+
- Include all important pages
|
|
152
|
+
- Set accurate `lastModified` dates
|
|
153
|
+
- Use appropriate `priority` values (0.0 to 1.0)
|
|
154
|
+
- Keep sitemaps under 50,000 URLs
|
|
155
|
+
- Update sitemap when content changes
|
|
156
|
+
- Submit sitemap to Google Search Console
|
|
157
|
+
|
|
158
|
+
## See Also
|
|
159
|
+
|
|
160
|
+
- [Existing Sitemap Documentation](../../SITEMAP.md)
|
|
161
|
+
- [Robots.txt](./robots-txt.md)
|
|
162
|
+
- [Canonical URLs](./canonical-urls.md)
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Structured Data
|
|
2
|
+
|
|
3
|
+
Add Schema.org JSON-LD markup for rich search results.
|
|
4
|
+
|
|
5
|
+
## StructuredData Component
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { StructuredData } from '@zoyth/simple-site-framework/components';
|
|
9
|
+
|
|
10
|
+
<StructuredData
|
|
11
|
+
data={{
|
|
12
|
+
'@type': 'Organization',
|
|
13
|
+
name: 'My Company',
|
|
14
|
+
url: 'https://example.com',
|
|
15
|
+
logo: 'https://example.com/logo.png',
|
|
16
|
+
}}
|
|
17
|
+
/>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Common Schema Types
|
|
21
|
+
|
|
22
|
+
### Organization
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
<StructuredData
|
|
26
|
+
data={{
|
|
27
|
+
'@type': 'Organization',
|
|
28
|
+
name: 'My Company',
|
|
29
|
+
url: 'https://example.com',
|
|
30
|
+
logo: 'https://example.com/logo.png',
|
|
31
|
+
contactPoint: {
|
|
32
|
+
'@type': 'ContactPoint',
|
|
33
|
+
telephone: '+1-555-123-4567',
|
|
34
|
+
contactType: 'customer service',
|
|
35
|
+
},
|
|
36
|
+
sameAs: [
|
|
37
|
+
'https://facebook.com/mycompany',
|
|
38
|
+
'https://twitter.com/mycompany',
|
|
39
|
+
'https://linkedin.com/company/mycompany',
|
|
40
|
+
],
|
|
41
|
+
}}
|
|
42
|
+
/>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### LocalBusiness
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
<StructuredData
|
|
49
|
+
data={{
|
|
50
|
+
'@type': 'LocalBusiness',
|
|
51
|
+
name: 'My Dental Practice',
|
|
52
|
+
image: 'https://example.com/photo.jpg',
|
|
53
|
+
address: {
|
|
54
|
+
'@type': 'PostalAddress',
|
|
55
|
+
streetAddress: '123 Main St',
|
|
56
|
+
addressLocality: 'Montreal',
|
|
57
|
+
addressRegion: 'QC',
|
|
58
|
+
postalCode: 'H1A 1A1',
|
|
59
|
+
addressCountry: 'CA',
|
|
60
|
+
},
|
|
61
|
+
telephone: '+1-555-123-4567',
|
|
62
|
+
openingHoursSpecification: [
|
|
63
|
+
{
|
|
64
|
+
'@type': 'OpeningHoursSpecification',
|
|
65
|
+
dayOfWeek: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
|
|
66
|
+
opens: '09:00',
|
|
67
|
+
closes: '17:00',
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
}}
|
|
71
|
+
/>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Service
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
<StructuredData
|
|
78
|
+
data={{
|
|
79
|
+
'@type': 'Service',
|
|
80
|
+
name: 'Tax Preparation',
|
|
81
|
+
description: 'Professional tax preparation for individuals and businesses',
|
|
82
|
+
provider: {
|
|
83
|
+
'@type': 'Organization',
|
|
84
|
+
name: 'My Accounting Firm',
|
|
85
|
+
},
|
|
86
|
+
areaServed: {
|
|
87
|
+
'@type': 'City',
|
|
88
|
+
name: 'Montreal',
|
|
89
|
+
},
|
|
90
|
+
}}
|
|
91
|
+
/>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### FAQ
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
<StructuredData
|
|
98
|
+
data={{
|
|
99
|
+
'@type': 'FAQPage',
|
|
100
|
+
mainEntity: [
|
|
101
|
+
{
|
|
102
|
+
'@type': 'Question',
|
|
103
|
+
name: 'What services do you offer?',
|
|
104
|
+
acceptedAnswer: {
|
|
105
|
+
'@type': 'Answer',
|
|
106
|
+
text: 'We offer accounting, tax, and advisory services.',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
'@type': 'Question',
|
|
111
|
+
name: 'Where are you located?',
|
|
112
|
+
acceptedAnswer: {
|
|
113
|
+
'@type': 'Answer',
|
|
114
|
+
text: 'We are located in downtown Montreal.',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
}}
|
|
119
|
+
/>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### BreadcrumbList
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
<StructuredData
|
|
126
|
+
data={{
|
|
127
|
+
'@type': 'BreadcrumbList',
|
|
128
|
+
itemListElement: [
|
|
129
|
+
{ '@type': 'ListItem', position: 1, name: 'Home', item: 'https://example.com/' },
|
|
130
|
+
{ '@type': 'ListItem', position: 2, name: 'Services', item: 'https://example.com/services' },
|
|
131
|
+
{ '@type': 'ListItem', position: 3, name: 'Tax', item: 'https://example.com/services/tax' },
|
|
132
|
+
],
|
|
133
|
+
}}
|
|
134
|
+
/>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Testing
|
|
138
|
+
|
|
139
|
+
### Google Rich Results Test
|
|
140
|
+
|
|
141
|
+
Test your structured data:
|
|
142
|
+
1. Go to [Rich Results Test](https://search.google.com/test/rich-results)
|
|
143
|
+
2. Enter your page URL
|
|
144
|
+
3. Verify no errors or warnings
|
|
145
|
+
|
|
146
|
+
### Schema Markup Validator
|
|
147
|
+
|
|
148
|
+
Validate Schema.org compliance:
|
|
149
|
+
1. Go to [Schema Markup Validator](https://validator.schema.org/)
|
|
150
|
+
2. Enter URL or paste JSON-LD
|
|
151
|
+
3. Check for errors
|
|
152
|
+
|
|
153
|
+
## Best Practices
|
|
154
|
+
|
|
155
|
+
- Add Organization schema to homepage
|
|
156
|
+
- Add LocalBusiness for local service businesses
|
|
157
|
+
- Add FAQ schema to FAQ pages
|
|
158
|
+
- Add BreadcrumbList for navigation
|
|
159
|
+
- Keep data accurate and up-to-date
|
|
160
|
+
- Don't add schema for content not visible on the page
|
|
161
|
+
|
|
162
|
+
## See Also
|
|
163
|
+
|
|
164
|
+
- [Existing Structured Data Documentation](../../STRUCTURED-DATA.md)
|
|
165
|
+
- [StructuredData Component](../../components/StructuredData.md)
|
|
166
|
+
- [Meta Tags](./meta-tags.md)
|