@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
package/docs/SITEMAP.md
ADDED
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
# Sitemap Generation Guide
|
|
2
|
+
|
|
3
|
+
Complete guide to generating XML sitemaps for better SEO and search engine crawling.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [Quick Start](#quick-start)
|
|
9
|
+
- [Single-Language Sitemaps](#single-language-sitemaps)
|
|
10
|
+
- [Multi-Language Sitemaps](#multi-language-sitemaps)
|
|
11
|
+
- [Configuration Options](#configuration-options)
|
|
12
|
+
- [Next.js Integration](#nextjs-integration)
|
|
13
|
+
- [Dynamic Routes](#dynamic-routes)
|
|
14
|
+
- [Best Practices](#best-practices)
|
|
15
|
+
- [Testing](#testing)
|
|
16
|
+
|
|
17
|
+
## Overview
|
|
18
|
+
|
|
19
|
+
A sitemap is an XML file that lists all important pages on your website, helping search engines:
|
|
20
|
+
|
|
21
|
+
- **Discover** all your pages efficiently
|
|
22
|
+
- **Understand** your site structure
|
|
23
|
+
- **Crawl** pages in the right order
|
|
24
|
+
- **Index** content faster
|
|
25
|
+
- **Recognize** multi-language pages with hreflang
|
|
26
|
+
|
|
27
|
+
The framework provides utilities to:
|
|
28
|
+
- Generate valid XML sitemaps
|
|
29
|
+
- Support multi-language sites with hreflang
|
|
30
|
+
- Configure priority and change frequency
|
|
31
|
+
- Validate sitemap entries
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
### Basic Sitemap
|
|
36
|
+
|
|
37
|
+
Create a simple sitemap for a single-language site:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
// app/sitemap.xml/route.ts
|
|
41
|
+
import { generateSitemap, createSitemapEntry } from 'simple-site-framework/lib/seo/sitemap';
|
|
42
|
+
|
|
43
|
+
export async function GET() {
|
|
44
|
+
const sitemap = generateSitemap({
|
|
45
|
+
baseUrl: 'https://acme.com',
|
|
46
|
+
entries: [
|
|
47
|
+
createSitemapEntry('https://acme.com', '/', {
|
|
48
|
+
priority: 1.0,
|
|
49
|
+
changeFrequency: 'weekly'
|
|
50
|
+
}),
|
|
51
|
+
createSitemapEntry('https://acme.com', '/about', {
|
|
52
|
+
priority: 0.8,
|
|
53
|
+
changeFrequency: 'monthly'
|
|
54
|
+
}),
|
|
55
|
+
createSitemapEntry('https://acme.com', '/products', {
|
|
56
|
+
priority: 0.9,
|
|
57
|
+
changeFrequency: 'daily'
|
|
58
|
+
}),
|
|
59
|
+
createSitemapEntry('https://acme.com', '/pricing', {
|
|
60
|
+
priority: 0.9,
|
|
61
|
+
changeFrequency: 'weekly'
|
|
62
|
+
}),
|
|
63
|
+
createSitemapEntry('https://acme.com', '/blog', {
|
|
64
|
+
priority: 0.7,
|
|
65
|
+
changeFrequency: 'daily'
|
|
66
|
+
})
|
|
67
|
+
]
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return new Response(sitemap, {
|
|
71
|
+
headers: {
|
|
72
|
+
'Content-Type': 'application/xml',
|
|
73
|
+
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate'
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This creates: `https://acme.com/sitemap.xml`
|
|
80
|
+
|
|
81
|
+
## Single-Language Sitemaps
|
|
82
|
+
|
|
83
|
+
### Manual Entry Creation
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { createSitemapEntry } from 'simple-site-framework/lib/seo/sitemap';
|
|
87
|
+
|
|
88
|
+
const entries = [
|
|
89
|
+
// Homepage - highest priority
|
|
90
|
+
createSitemapEntry('https://acme.com', '/', {
|
|
91
|
+
priority: 1.0,
|
|
92
|
+
changeFrequency: 'weekly',
|
|
93
|
+
lastModified: new Date('2024-01-15')
|
|
94
|
+
}),
|
|
95
|
+
|
|
96
|
+
// Key product pages
|
|
97
|
+
createSitemapEntry('https://acme.com', '/pricing', {
|
|
98
|
+
priority: 0.9,
|
|
99
|
+
changeFrequency: 'weekly'
|
|
100
|
+
}),
|
|
101
|
+
createSitemapEntry('https://acme.com', '/features', {
|
|
102
|
+
priority: 0.9,
|
|
103
|
+
changeFrequency: 'monthly'
|
|
104
|
+
}),
|
|
105
|
+
|
|
106
|
+
// About/contact
|
|
107
|
+
createSitemapEntry('https://acme.com', '/about', {
|
|
108
|
+
priority: 0.7,
|
|
109
|
+
changeFrequency: 'monthly'
|
|
110
|
+
}),
|
|
111
|
+
createSitemapEntry('https://acme.com', '/contact', {
|
|
112
|
+
priority: 0.6,
|
|
113
|
+
changeFrequency: 'yearly'
|
|
114
|
+
}),
|
|
115
|
+
|
|
116
|
+
// Blog - frequently updated
|
|
117
|
+
createSitemapEntry('https://acme.com', '/blog', {
|
|
118
|
+
priority: 0.7,
|
|
119
|
+
changeFrequency: 'daily'
|
|
120
|
+
})
|
|
121
|
+
];
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### With Last Modified Dates
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
const entries = [
|
|
128
|
+
createSitemapEntry('https://acme.com', '/', {
|
|
129
|
+
priority: 1.0,
|
|
130
|
+
lastModified: new Date(), // Current date
|
|
131
|
+
changeFrequency: 'weekly'
|
|
132
|
+
}),
|
|
133
|
+
createSitemapEntry('https://acme.com', '/blog/post-1', {
|
|
134
|
+
priority: 0.6,
|
|
135
|
+
lastModified: '2024-01-15T10:00:00Z', // ISO 8601 string
|
|
136
|
+
changeFrequency: 'never'
|
|
137
|
+
})
|
|
138
|
+
];
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Multi-Language Sitemaps
|
|
142
|
+
|
|
143
|
+
### Using createMultiLanguageEntries()
|
|
144
|
+
|
|
145
|
+
Generate entries for pages that exist in multiple languages:
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
import {
|
|
149
|
+
generateSitemap,
|
|
150
|
+
createMultiLanguageEntries
|
|
151
|
+
} from 'simple-site-framework/lib/seo/sitemap';
|
|
152
|
+
|
|
153
|
+
const locales = ['en', 'fr', 'es'];
|
|
154
|
+
const baseUrl = 'https://acme.com';
|
|
155
|
+
|
|
156
|
+
const entries = [
|
|
157
|
+
// Homepage in all languages
|
|
158
|
+
...createMultiLanguageEntries(
|
|
159
|
+
baseUrl,
|
|
160
|
+
'/',
|
|
161
|
+
locales,
|
|
162
|
+
'en', // Default locale for x-default
|
|
163
|
+
{ priority: 1.0, changeFrequency: 'weekly' }
|
|
164
|
+
),
|
|
165
|
+
|
|
166
|
+
// About page in all languages
|
|
167
|
+
...createMultiLanguageEntries(
|
|
168
|
+
baseUrl,
|
|
169
|
+
'/about',
|
|
170
|
+
locales,
|
|
171
|
+
'en',
|
|
172
|
+
{ priority: 0.8, changeFrequency: 'monthly' }
|
|
173
|
+
),
|
|
174
|
+
|
|
175
|
+
// Pricing page in all languages
|
|
176
|
+
...createMultiLanguageEntries(
|
|
177
|
+
baseUrl,
|
|
178
|
+
'/pricing',
|
|
179
|
+
locales,
|
|
180
|
+
'en',
|
|
181
|
+
{ priority: 0.9, changeFrequency: 'weekly' }
|
|
182
|
+
)
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
const sitemap = generateSitemap({ baseUrl, entries });
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
This creates entries like:
|
|
189
|
+
```xml
|
|
190
|
+
<url>
|
|
191
|
+
<loc>https://acme.com/en/about</loc>
|
|
192
|
+
<xhtml:link rel="alternate" hreflang="en" href="https://acme.com/en/about" />
|
|
193
|
+
<xhtml:link rel="alternate" hreflang="fr" href="https://acme.com/fr/about" />
|
|
194
|
+
<xhtml:link rel="alternate" hreflang="es" href="https://acme.com/es/about" />
|
|
195
|
+
<xhtml:link rel="alternate" hreflang="x-default" href="https://acme.com/en/about" />
|
|
196
|
+
<priority>0.8</priority>
|
|
197
|
+
<changefreq>monthly</changefreq>
|
|
198
|
+
</url>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Manual Multi-Language Entries
|
|
202
|
+
|
|
203
|
+
For more control, create entries manually:
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
const entries = [
|
|
207
|
+
{
|
|
208
|
+
url: 'https://acme.com/en/about',
|
|
209
|
+
priority: 0.8,
|
|
210
|
+
alternates: [
|
|
211
|
+
{ hreflang: 'en', href: 'https://acme.com/en/about' },
|
|
212
|
+
{ hreflang: 'fr', href: 'https://acme.com/fr/about' },
|
|
213
|
+
{ hreflang: 'es', href: 'https://acme.com/es/about' },
|
|
214
|
+
{ hreflang: 'x-default', href: 'https://acme.com/en/about' }
|
|
215
|
+
]
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
url: 'https://acme.com/fr/about',
|
|
219
|
+
priority: 0.8,
|
|
220
|
+
alternates: [
|
|
221
|
+
{ hreflang: 'en', href: 'https://acme.com/en/about' },
|
|
222
|
+
{ hreflang: 'fr', href: 'https://acme.com/fr/about' },
|
|
223
|
+
{ hreflang: 'es', href: 'https://acme.com/es/about' },
|
|
224
|
+
{ hreflang: 'x-default', href: 'https://acme.com/en/about' }
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
];
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Configuration Options
|
|
231
|
+
|
|
232
|
+
### Priority (0.0 - 1.0)
|
|
233
|
+
|
|
234
|
+
Indicates the importance of a page relative to other pages on your site:
|
|
235
|
+
|
|
236
|
+
```tsx
|
|
237
|
+
createSitemapEntry(baseUrl, '/', { priority: 1.0 }) // Homepage - highest
|
|
238
|
+
createSitemapEntry(baseUrl, '/pricing', { priority: 0.9 }) // Key pages
|
|
239
|
+
createSitemapEntry(baseUrl, '/about', { priority: 0.7 }) // Secondary pages
|
|
240
|
+
createSitemapEntry(baseUrl, '/terms', { priority: 0.3 }) // Legal pages
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Recommended priorities:**
|
|
244
|
+
- 1.0: Homepage
|
|
245
|
+
- 0.9: Key product/service pages
|
|
246
|
+
- 0.7-0.8: Important content pages
|
|
247
|
+
- 0.5-0.6: Blog posts, articles
|
|
248
|
+
- 0.3-0.4: Legal pages, archives
|
|
249
|
+
|
|
250
|
+
### Change Frequency
|
|
251
|
+
|
|
252
|
+
How often the page is likely to change:
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
changeFrequency: 'always' // Changes every time it's accessed (rare)
|
|
256
|
+
changeFrequency: 'hourly' // Real-time data, live scores
|
|
257
|
+
changeFrequency: 'daily' // News, blog homepage, active content
|
|
258
|
+
changeFrequency: 'weekly' // Homepage, pricing, features
|
|
259
|
+
changeFrequency: 'monthly' // About, team, documentation
|
|
260
|
+
changeFrequency: 'yearly' // Terms, privacy, legal pages
|
|
261
|
+
changeFrequency: 'never' // Archived content
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Note:** This is a *hint* to search engines, not a directive.
|
|
265
|
+
|
|
266
|
+
### Last Modified
|
|
267
|
+
|
|
268
|
+
When the page was last changed:
|
|
269
|
+
|
|
270
|
+
```tsx
|
|
271
|
+
// Using Date object
|
|
272
|
+
lastModified: new Date()
|
|
273
|
+
lastModified: new Date('2024-01-15')
|
|
274
|
+
|
|
275
|
+
// Using ISO 8601 string
|
|
276
|
+
lastModified: '2024-01-15T10:00:00Z'
|
|
277
|
+
lastModified: '2024-01-15'
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Pretty Print
|
|
281
|
+
|
|
282
|
+
Format XML for human readability:
|
|
283
|
+
|
|
284
|
+
```tsx
|
|
285
|
+
const sitemap = generateSitemap({
|
|
286
|
+
baseUrl: 'https://acme.com',
|
|
287
|
+
entries: [...],
|
|
288
|
+
prettyPrint: true // Adds indentation and line breaks
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Use pretty print for:**
|
|
293
|
+
- Development/debugging
|
|
294
|
+
- Manual inspection
|
|
295
|
+
|
|
296
|
+
**Don't use pretty print for:**
|
|
297
|
+
- Production (larger file size)
|
|
298
|
+
- When file size matters
|
|
299
|
+
|
|
300
|
+
## Next.js Integration
|
|
301
|
+
|
|
302
|
+
### App Router (Next.js 13+)
|
|
303
|
+
|
|
304
|
+
Create a Route Handler for the sitemap:
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
// app/sitemap.xml/route.ts
|
|
308
|
+
import { generateSitemap, createSitemapEntry } from 'simple-site-framework/lib/seo/sitemap';
|
|
309
|
+
|
|
310
|
+
export async function GET() {
|
|
311
|
+
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'https://example.com';
|
|
312
|
+
|
|
313
|
+
const staticEntries = [
|
|
314
|
+
createSitemapEntry(baseUrl, '/', { priority: 1.0, changeFrequency: 'weekly' }),
|
|
315
|
+
createSitemapEntry(baseUrl, '/about', { priority: 0.8 }),
|
|
316
|
+
createSitemapEntry(baseUrl, '/pricing', { priority: 0.9 })
|
|
317
|
+
];
|
|
318
|
+
|
|
319
|
+
const sitemap = generateSitemap({
|
|
320
|
+
baseUrl,
|
|
321
|
+
entries: staticEntries
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
return new Response(sitemap, {
|
|
325
|
+
headers: {
|
|
326
|
+
'Content-Type': 'application/xml',
|
|
327
|
+
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate'
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Multi-Language App Router
|
|
334
|
+
|
|
335
|
+
```tsx
|
|
336
|
+
// app/sitemap.xml/route.ts
|
|
337
|
+
import { generateSitemap, createMultiLanguageEntries } from 'simple-site-framework/lib/seo/sitemap';
|
|
338
|
+
import { getI18nConfig } from '@/config/i18n';
|
|
339
|
+
|
|
340
|
+
export async function GET() {
|
|
341
|
+
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'https://example.com';
|
|
342
|
+
const { locales, defaultLocale } = getI18nConfig();
|
|
343
|
+
|
|
344
|
+
// Static pages
|
|
345
|
+
const staticPages = ['/', '/about', '/pricing', '/features'];
|
|
346
|
+
|
|
347
|
+
const entries = staticPages.flatMap(page =>
|
|
348
|
+
createMultiLanguageEntries(
|
|
349
|
+
baseUrl,
|
|
350
|
+
page,
|
|
351
|
+
locales,
|
|
352
|
+
defaultLocale,
|
|
353
|
+
{
|
|
354
|
+
priority: page === '/' ? 1.0 : 0.8,
|
|
355
|
+
changeFrequency: 'weekly'
|
|
356
|
+
}
|
|
357
|
+
)
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
const sitemap = generateSitemap({ baseUrl, entries });
|
|
361
|
+
|
|
362
|
+
return new Response(sitemap, {
|
|
363
|
+
headers: {
|
|
364
|
+
'Content-Type': 'application/xml',
|
|
365
|
+
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate'
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Dynamic Routes
|
|
372
|
+
|
|
373
|
+
### Blog Posts
|
|
374
|
+
|
|
375
|
+
Include dynamically generated content:
|
|
376
|
+
|
|
377
|
+
```tsx
|
|
378
|
+
// app/sitemap.xml/route.ts
|
|
379
|
+
import { generateSitemap, createSitemapEntry } from 'simple-site-framework/lib/seo/sitemap';
|
|
380
|
+
|
|
381
|
+
export async function GET() {
|
|
382
|
+
const baseUrl = 'https://acme.com';
|
|
383
|
+
|
|
384
|
+
// Static pages
|
|
385
|
+
const staticEntries = [
|
|
386
|
+
createSitemapEntry(baseUrl, '/', { priority: 1.0 }),
|
|
387
|
+
createSitemapEntry(baseUrl, '/blog', { priority: 0.7, changeFrequency: 'daily' })
|
|
388
|
+
];
|
|
389
|
+
|
|
390
|
+
// Fetch blog posts from database/CMS
|
|
391
|
+
const posts = await fetchBlogPosts();
|
|
392
|
+
|
|
393
|
+
const blogEntries = posts.map(post =>
|
|
394
|
+
createSitemapEntry(baseUrl, `/blog/${post.slug}`, {
|
|
395
|
+
priority: 0.6,
|
|
396
|
+
changeFrequency: 'never',
|
|
397
|
+
lastModified: post.updatedAt
|
|
398
|
+
})
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
const sitemap = generateSitemap({
|
|
402
|
+
baseUrl,
|
|
403
|
+
entries: [...staticEntries, ...blogEntries]
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
return new Response(sitemap, {
|
|
407
|
+
headers: {
|
|
408
|
+
'Content-Type': 'application/xml',
|
|
409
|
+
'Cache-Control': 'public, s-maxage=3600'
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function fetchBlogPosts() {
|
|
415
|
+
// Fetch from your CMS, database, etc.
|
|
416
|
+
return [
|
|
417
|
+
{ slug: 'email-tips', updatedAt: '2024-01-15T10:00:00Z' },
|
|
418
|
+
{ slug: 'marketing-guide', updatedAt: '2024-01-20T14:30:00Z' }
|
|
419
|
+
];
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Multi-Language Dynamic Routes
|
|
424
|
+
|
|
425
|
+
```tsx
|
|
426
|
+
const posts = await fetchBlogPosts();
|
|
427
|
+
|
|
428
|
+
const blogEntries = posts.flatMap(post =>
|
|
429
|
+
createMultiLanguageEntries(
|
|
430
|
+
baseUrl,
|
|
431
|
+
`/blog/${post.slug}`,
|
|
432
|
+
locales,
|
|
433
|
+
defaultLocale,
|
|
434
|
+
{
|
|
435
|
+
priority: 0.6,
|
|
436
|
+
changeFrequency: 'never',
|
|
437
|
+
lastModified: post.updatedAt
|
|
438
|
+
}
|
|
439
|
+
)
|
|
440
|
+
);
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
## Best Practices
|
|
444
|
+
|
|
445
|
+
### 1. Use Absolute URLs
|
|
446
|
+
|
|
447
|
+
Always use absolute URLs (https://...) never relative (/path):
|
|
448
|
+
|
|
449
|
+
```tsx
|
|
450
|
+
// ✅ Good
|
|
451
|
+
createSitemapEntry('https://acme.com', '/about')
|
|
452
|
+
|
|
453
|
+
// ❌ Bad
|
|
454
|
+
createSitemapEntry('acme.com', '/about')
|
|
455
|
+
createSitemapEntry('', '/about')
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### 2. Include Important Pages Only
|
|
459
|
+
|
|
460
|
+
Don't include:
|
|
461
|
+
- Login/logout pages
|
|
462
|
+
- Admin pages
|
|
463
|
+
- Thank you pages
|
|
464
|
+
- Confirmation pages
|
|
465
|
+
- Pages with `noindex`
|
|
466
|
+
|
|
467
|
+
```tsx
|
|
468
|
+
// ✅ Good - Public pages only
|
|
469
|
+
const entries = [
|
|
470
|
+
createSitemapEntry(baseUrl, '/'),
|
|
471
|
+
createSitemapEntry(baseUrl, '/products'),
|
|
472
|
+
createSitemapEntry(baseUrl, '/pricing')
|
|
473
|
+
];
|
|
474
|
+
|
|
475
|
+
// ❌ Bad - Includes private/irrelevant pages
|
|
476
|
+
const entries = [
|
|
477
|
+
createSitemapEntry(baseUrl, '/admin'),
|
|
478
|
+
createSitemapEntry(baseUrl, '/thank-you'),
|
|
479
|
+
createSitemapEntry(baseUrl, '/cart')
|
|
480
|
+
];
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### 3. Set Realistic Priorities
|
|
484
|
+
|
|
485
|
+
Don't set everything to 1.0:
|
|
486
|
+
|
|
487
|
+
```tsx
|
|
488
|
+
// ✅ Good - Varied priorities
|
|
489
|
+
createSitemapEntry(baseUrl, '/', { priority: 1.0 })
|
|
490
|
+
createSitemapEntry(baseUrl, '/pricing', { priority: 0.9 })
|
|
491
|
+
createSitemapEntry(baseUrl, '/blog/post', { priority: 0.6 })
|
|
492
|
+
|
|
493
|
+
// ❌ Bad - Everything is highest priority
|
|
494
|
+
createSitemapEntry(baseUrl, '/', { priority: 1.0 })
|
|
495
|
+
createSitemapEntry(baseUrl, '/terms', { priority: 1.0 })
|
|
496
|
+
createSitemapEntry(baseUrl, '/404', { priority: 1.0 })
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
### 4. Use Accurate Change Frequencies
|
|
500
|
+
|
|
501
|
+
Base it on actual update patterns:
|
|
502
|
+
|
|
503
|
+
```tsx
|
|
504
|
+
// ✅ Good
|
|
505
|
+
createSitemapEntry(baseUrl, '/', { changeFrequency: 'weekly' }) // Homepage updated weekly
|
|
506
|
+
createSitemapEntry(baseUrl, '/blog', { changeFrequency: 'daily' }) // New posts daily
|
|
507
|
+
createSitemapEntry(baseUrl, '/terms', { changeFrequency: 'yearly' }) // Rarely changes
|
|
508
|
+
|
|
509
|
+
// ❌ Bad
|
|
510
|
+
createSitemapEntry(baseUrl, '/terms', { changeFrequency: 'hourly' }) // Unrealistic
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### 5. Keep Sitemap Under 50MB / 50,000 URLs
|
|
514
|
+
|
|
515
|
+
If you have more:
|
|
516
|
+
1. Create multiple sitemaps
|
|
517
|
+
2. Use a sitemap index file
|
|
518
|
+
|
|
519
|
+
```tsx
|
|
520
|
+
// sitemap-index.xml
|
|
521
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
522
|
+
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
523
|
+
<sitemap>
|
|
524
|
+
<loc>https://acme.com/sitemap-main.xml</loc>
|
|
525
|
+
<lastmod>2024-01-15T10:00:00Z</lastmod>
|
|
526
|
+
</sitemap>
|
|
527
|
+
<sitemap>
|
|
528
|
+
<loc>https://acme.com/sitemap-blog.xml</loc>
|
|
529
|
+
<lastmod>2024-01-20T14:30:00Z</lastmod>
|
|
530
|
+
</sitemap>
|
|
531
|
+
</sitemapindex>
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### 6. Cache Appropriately
|
|
535
|
+
|
|
536
|
+
```tsx
|
|
537
|
+
return new Response(sitemap, {
|
|
538
|
+
headers: {
|
|
539
|
+
'Content-Type': 'application/xml',
|
|
540
|
+
// Cache for 1 hour, revalidate in background
|
|
541
|
+
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate'
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
### 7. Submit to Search Engines
|
|
547
|
+
|
|
548
|
+
After deploying your sitemap:
|
|
549
|
+
|
|
550
|
+
1. **Google Search Console**: Add sitemap URL
|
|
551
|
+
2. **Bing Webmaster Tools**: Submit sitemap
|
|
552
|
+
3. **robots.txt**: Reference sitemap
|
|
553
|
+
|
|
554
|
+
```
|
|
555
|
+
# robots.txt
|
|
556
|
+
User-agent: *
|
|
557
|
+
Allow: /
|
|
558
|
+
|
|
559
|
+
Sitemap: https://acme.com/sitemap.xml
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### 8. Monitor for Errors
|
|
563
|
+
|
|
564
|
+
Check Search Console regularly for:
|
|
565
|
+
- Sitemap parsing errors
|
|
566
|
+
- URL errors
|
|
567
|
+
- Coverage issues
|
|
568
|
+
|
|
569
|
+
## Testing
|
|
570
|
+
|
|
571
|
+
### Validate XML
|
|
572
|
+
|
|
573
|
+
```tsx
|
|
574
|
+
import { validateSitemap } from 'simple-site-framework/lib/seo/sitemap';
|
|
575
|
+
|
|
576
|
+
const config = {
|
|
577
|
+
baseUrl: 'https://acme.com',
|
|
578
|
+
entries: [...]
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
const result = validateSitemap(config);
|
|
582
|
+
|
|
583
|
+
if (!result.isValid) {
|
|
584
|
+
console.error('Sitemap validation errors:', result.errors);
|
|
585
|
+
}
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
### Manual Testing
|
|
589
|
+
|
|
590
|
+
1. Visit `https://yoursite.com/sitemap.xml`
|
|
591
|
+
2. View source to inspect XML
|
|
592
|
+
3. Verify all URLs are absolute
|
|
593
|
+
4. Check hreflang alternates are correct
|
|
594
|
+
|
|
595
|
+
### Google Search Console
|
|
596
|
+
|
|
597
|
+
1. Go to: Sitemaps section
|
|
598
|
+
2. Submit sitemap URL
|
|
599
|
+
3. Check for errors after processing
|
|
600
|
+
|
|
601
|
+
### XML Validators
|
|
602
|
+
|
|
603
|
+
- https://www.xml-sitemaps.com/validate-xml-sitemap.html
|
|
604
|
+
- https://xmlvalidation.com/
|
|
605
|
+
|
|
606
|
+
## Example: Complete Implementation
|
|
607
|
+
|
|
608
|
+
```tsx
|
|
609
|
+
// app/sitemap.xml/route.ts
|
|
610
|
+
import {
|
|
611
|
+
generateSitemap,
|
|
612
|
+
createSitemapEntry,
|
|
613
|
+
createMultiLanguageEntries,
|
|
614
|
+
validateSitemap
|
|
615
|
+
} from 'simple-site-framework/lib/seo/sitemap';
|
|
616
|
+
|
|
617
|
+
export async function GET() {
|
|
618
|
+
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL!;
|
|
619
|
+
const locales = ['en', 'fr', 'es'];
|
|
620
|
+
const defaultLocale = 'en';
|
|
621
|
+
|
|
622
|
+
// Static pages
|
|
623
|
+
const staticPages = [
|
|
624
|
+
{ path: '/', priority: 1.0, changeFreq: 'weekly' as const },
|
|
625
|
+
{ path: '/about', priority: 0.8, changeFreq: 'monthly' as const },
|
|
626
|
+
{ path: '/pricing', priority: 0.9, changeFreq: 'weekly' as const },
|
|
627
|
+
{ path: '/features', priority: 0.9, changeFreq: 'monthly' as const }
|
|
628
|
+
];
|
|
629
|
+
|
|
630
|
+
const staticEntries = staticPages.flatMap(page =>
|
|
631
|
+
createMultiLanguageEntries(
|
|
632
|
+
baseUrl,
|
|
633
|
+
page.path,
|
|
634
|
+
locales,
|
|
635
|
+
defaultLocale,
|
|
636
|
+
{
|
|
637
|
+
priority: page.priority,
|
|
638
|
+
changeFrequency: page.changeFreq
|
|
639
|
+
}
|
|
640
|
+
)
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
// Dynamic blog posts
|
|
644
|
+
const posts = await fetchBlogPosts();
|
|
645
|
+
const blogEntries = posts.flatMap(post =>
|
|
646
|
+
createMultiLanguageEntries(
|
|
647
|
+
baseUrl,
|
|
648
|
+
`/blog/${post.slug}`,
|
|
649
|
+
locales,
|
|
650
|
+
defaultLocale,
|
|
651
|
+
{
|
|
652
|
+
priority: 0.6,
|
|
653
|
+
changeFrequency: 'never',
|
|
654
|
+
lastModified: post.updatedAt
|
|
655
|
+
}
|
|
656
|
+
)
|
|
657
|
+
);
|
|
658
|
+
|
|
659
|
+
const config = {
|
|
660
|
+
baseUrl,
|
|
661
|
+
entries: [...staticEntries, ...blogEntries]
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
// Validate before generating
|
|
665
|
+
const validation = validateSitemap(config);
|
|
666
|
+
if (!validation.isValid) {
|
|
667
|
+
console.error('Sitemap validation errors:', validation.errors);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const sitemap = generateSitemap(config);
|
|
671
|
+
|
|
672
|
+
return new Response(sitemap, {
|
|
673
|
+
headers: {
|
|
674
|
+
'Content-Type': 'application/xml',
|
|
675
|
+
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate'
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
async function fetchBlogPosts() {
|
|
681
|
+
// Fetch from database/CMS
|
|
682
|
+
return [];
|
|
683
|
+
}
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
## Checklist
|
|
687
|
+
|
|
688
|
+
- [ ] Sitemap accessible at /sitemap.xml
|
|
689
|
+
- [ ] All URLs are absolute (https://)
|
|
690
|
+
- [ ] No duplicate URLs
|
|
691
|
+
- [ ] All URLs return 200 status
|
|
692
|
+
- [ ] Priority values between 0.0 and 1.0
|
|
693
|
+
- [ ] Change frequencies are realistic
|
|
694
|
+
- [ ] Multi-language pages have hreflang alternates
|
|
695
|
+
- [ ] x-default points to default language
|
|
696
|
+
- [ ] Submitted to Google Search Console
|
|
697
|
+
- [ ] Submitted to Bing Webmaster Tools
|
|
698
|
+
- [ ] Referenced in robots.txt
|
|
699
|
+
- [ ] Under 50MB and 50,000 URLs
|
|
700
|
+
- [ ] Appropriate caching headers
|
|
701
|
+
- [ ] No errors in Search Console
|
|
702
|
+
|
|
703
|
+
## Resources
|
|
704
|
+
|
|
705
|
+
- [Sitemaps.org Protocol](https://www.sitemaps.org/protocol.html)
|
|
706
|
+
- [Google Sitemap Guidelines](https://developers.google.com/search/docs/crawling-indexing/sitemaps/overview)
|
|
707
|
+
- [Google hreflang Guidelines](https://developers.google.com/search/docs/specialty/international/localized-versions)
|
|
708
|
+
- [XML Sitemap Validator](https://www.xml-sitemaps.com/validate-xml-sitemap.html)
|