@od-oneapp/seo 2026.1.1301

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.
Files changed (56) hide show
  1. package/README.md +586 -0
  2. package/dist/client-next.d.mts +46 -0
  3. package/dist/client-next.d.mts.map +1 -0
  4. package/dist/client-next.mjs +92 -0
  5. package/dist/client-next.mjs.map +1 -0
  6. package/dist/client.d.mts +16 -0
  7. package/dist/client.d.mts.map +1 -0
  8. package/dist/client.mjs +47 -0
  9. package/dist/client.mjs.map +1 -0
  10. package/dist/env.d.mts +31 -0
  11. package/dist/env.d.mts.map +1 -0
  12. package/dist/env.mjs +125 -0
  13. package/dist/env.mjs.map +1 -0
  14. package/dist/index.d.mts +30 -0
  15. package/dist/index.d.mts.map +1 -0
  16. package/dist/index.mjs +129 -0
  17. package/dist/index.mjs.map +1 -0
  18. package/dist/server-next.d.mts +230 -0
  19. package/dist/server-next.d.mts.map +1 -0
  20. package/dist/server-next.mjs +541 -0
  21. package/dist/server-next.mjs.map +1 -0
  22. package/dist/server.d.mts +3 -0
  23. package/dist/server.mjs +3 -0
  24. package/dist/structured-data-builders-ByJ4KCEf.mjs +176 -0
  25. package/dist/structured-data-builders-ByJ4KCEf.mjs.map +1 -0
  26. package/dist/structured-data-builders-CAgdYvmz.d.mts +74 -0
  27. package/dist/structured-data-builders-CAgdYvmz.d.mts.map +1 -0
  28. package/dist/structured-data.d.mts +16 -0
  29. package/dist/structured-data.d.mts.map +1 -0
  30. package/dist/structured-data.mjs +62 -0
  31. package/dist/structured-data.mjs.map +1 -0
  32. package/dist/validation.d.mts +20 -0
  33. package/dist/validation.d.mts.map +1 -0
  34. package/dist/validation.mjs +233 -0
  35. package/dist/validation.mjs.map +1 -0
  36. package/package.json +110 -0
  37. package/src/client-next.tsx +134 -0
  38. package/src/client.tsx +96 -0
  39. package/src/components/json-ld.tsx +74 -0
  40. package/src/components/structured-data.tsx +91 -0
  41. package/src/examples/app-router-sitemap.ts +109 -0
  42. package/src/examples/metadata-patterns.ts +528 -0
  43. package/src/examples/next-sitemap-config.ts +92 -0
  44. package/src/examples/nextjs-15-features.tsx +383 -0
  45. package/src/examples/nextjs-15-integration.ts +241 -0
  46. package/src/index.ts +87 -0
  47. package/src/server-next.ts +958 -0
  48. package/src/server.ts +27 -0
  49. package/src/types/metadata.ts +85 -0
  50. package/src/types/seo.ts +60 -0
  51. package/src/types/structured-data.ts +94 -0
  52. package/src/utils/i18n-enhanced.ts +148 -0
  53. package/src/utils/metadata-enhanced.ts +238 -0
  54. package/src/utils/metadata.ts +169 -0
  55. package/src/utils/structured-data-builders.ts +322 -0
  56. package/src/utils/validation.ts +284 -0
@@ -0,0 +1,383 @@
1
+ /**
2
+ * @fileoverview Next.js 15 SEO Features Comprehensive Example
3
+ * @module @repo/seo/examples/nextjs-15-features
4
+ *
5
+ * This example demonstrates all the enhanced Next.js 15 SEO features
6
+ * provided by the @repo/seo package, including:
7
+ *
8
+ * - Optimized JsonLd components with Script strategy
9
+ * - Streaming-compatible structured data
10
+ * - Metadata templates for common patterns
11
+ * - Dynamic viewport configuration
12
+ * - Multi-language sitemap generation
13
+ * - Preview mode metadata handling
14
+ * - Edge-compatible metadata generation
15
+ *
16
+ * @example
17
+ * // Import the features you need
18
+ * import { OptimizedJsonLd, metadataTemplates } from '@repo/seo/client/next';
19
+ * import { generateMetadataAsync, generateI18nSitemap } from '@repo/seo/server/next';
20
+ */
21
+
22
+ // ============================================
23
+ // CLIENT-SIDE EXAMPLES (client-next.tsx)
24
+ // ============================================
25
+
26
+ // ============================================
27
+ // SERVER-SIDE EXAMPLES (server-next.ts)
28
+ // ============================================
29
+ import { type Metadata } from 'next';
30
+
31
+ import { type Product, type WithContext } from 'schema-dts';
32
+
33
+ import { OptimizedJsonLd, StreamingJsonLd, useOpenGraphPreview } from '../client-next';
34
+ import {
35
+ generateI18nSitemap,
36
+ generateMetadataAsync,
37
+ generateMetadataEdge,
38
+ generatePreviewMetadata,
39
+ generateViewport,
40
+ metadataTemplates,
41
+ SEOManager,
42
+ viewportPresets,
43
+ } from '../server-next';
44
+
45
+ /**
46
+ * Example: Optimized JsonLd with Next.js Script component
47
+ * This provides better performance by controlling when the script loads
48
+ */
49
+ export function ProductPageWithOptimizedSEO({ product }: { product: any }) {
50
+ const structuredData: WithContext<Product> = {
51
+ '@context': 'https://schema.org',
52
+ '@type': 'Product',
53
+ name: product.name,
54
+ description: product.description,
55
+ image: product.images,
56
+ offers: {
57
+ '@type': 'Offer',
58
+ price: product.price,
59
+ priceCurrency: product.currency,
60
+ availability: product.inStock
61
+ ? 'https://schema.org/InStock'
62
+ : 'https://schema.org/OutOfStock',
63
+ },
64
+ };
65
+
66
+ return (
67
+ <>
68
+ <OptimizedJsonLd data={structuredData} id="product-data" strategy="afterInteractive" />
69
+
70
+ <OptimizedJsonLd
71
+ data={structuredData}
72
+ id="critical-product-data"
73
+ strategy="beforeInteractive"
74
+ />
75
+ </>
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Example: Streaming JsonLd for React Server Components
81
+ * Perfect for async data that needs SEO optimization
82
+ */
83
+ export function StreamingProductPage({ productId }: { productId: string }) {
84
+ // This could be a fetch from your database
85
+ const productPromise = (async () => {
86
+ const res = await fetch(`/api/products/${productId}`);
87
+ const product = await res.json();
88
+ return {
89
+ '@context': 'https://schema.org',
90
+ '@type': 'Product',
91
+ name: product.name,
92
+ description: product.description,
93
+ } as WithContext<Product>;
94
+ })();
95
+
96
+ const fallbackData: WithContext<Product> = {
97
+ '@context': 'https://schema.org',
98
+ '@type': 'Product',
99
+ name: 'Loading...',
100
+ description: 'Product information is loading',
101
+ };
102
+
103
+ return (
104
+ <StreamingJsonLd dataPromise={productPromise} fallback={fallbackData} id="streaming-product" />
105
+ );
106
+ }
107
+
108
+ /**
109
+ * Example: Dynamic Open Graph Preview Hook
110
+ * Useful for content editors or preview modes
111
+ */
112
+ export function ContentEditor() {
113
+ const { preview, updatePreview, generatePreviewHtml } = useOpenGraphPreview({
114
+ title: 'My Article',
115
+ description: 'Article description',
116
+ image: '/images/article-hero.jpg',
117
+ url: 'https://example.com/article',
118
+ });
119
+
120
+ return (
121
+ <div>
122
+ <h2>SEO Preview</h2>
123
+ <input
124
+ placeholder="Title"
125
+ value={preview.title}
126
+ onChange={(e: any) => updatePreview({ title: e.target.value })}
127
+ />
128
+ <textarea
129
+ placeholder="Description"
130
+ value={preview.description}
131
+ onChange={(e: any) => updatePreview({ description: e.target.value })}
132
+ />
133
+ <div>
134
+ <h3>Generated Meta Tags:</h3>
135
+ <pre>{generatePreviewHtml}</pre>
136
+ </div>
137
+ </div>
138
+ );
139
+ }
140
+
141
+ /**
142
+ * Example: Using metadata templates for a product page
143
+ */
144
+ export async function generateMetadata({ params }: { params: { id: string } }) {
145
+ const product = await getProduct(params.id);
146
+
147
+ return metadataTemplates.product({
148
+ name: product.name,
149
+ description: product.description,
150
+ price: product.price,
151
+ currency: 'USD',
152
+ image: product.image,
153
+ availability: product.inStock ? 'InStock' : 'OutOfStock',
154
+ brand: product.brand,
155
+ });
156
+ }
157
+
158
+ /**
159
+ * Example: Article metadata with author and publishing info
160
+ */
161
+ export function ArticleMetadata({ article }: { article: any }): Metadata {
162
+ return metadataTemplates.article({
163
+ title: article.title,
164
+ description: article.excerpt,
165
+ author: article.author.name,
166
+ publishedTime: new Date(article.publishedAt),
167
+ modifiedTime: article.updatedAt ? new Date(article.updatedAt) : undefined,
168
+ image: article.featuredImage,
169
+ tags: article.tags,
170
+ section: article.category,
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Example: User profile metadata
176
+ */
177
+ export function ProfileMetadata({ user }: { user: any }): Metadata {
178
+ return metadataTemplates.profile({
179
+ name: user.displayName,
180
+ bio: user.bio,
181
+ image: user.avatar,
182
+ username: user.username,
183
+ firstName: user.firstName,
184
+ lastName: user.lastName,
185
+ });
186
+ }
187
+
188
+ /**
189
+ * Example: Dynamic viewport based on user agent
190
+ */
191
+ export function generateDynamicViewport(request: any) {
192
+ const userAgent = request.headers.get('user-agent') ?? '';
193
+ return generateViewport(userAgent);
194
+ }
195
+
196
+ /**
197
+ * Example: Using viewport presets
198
+ */
199
+ export const viewportExamples = {
200
+ // Default responsive viewport
201
+ default: viewportPresets.default,
202
+
203
+ // Mobile app that shouldn't zoom
204
+ mobileApp: viewportPresets.mobileOptimized,
205
+
206
+ // Content-heavy tablet site
207
+ tablet: viewportPresets.tablet,
208
+
209
+ // Desktop-first application
210
+ desktop: viewportPresets.desktop,
211
+ };
212
+
213
+ /**
214
+ * Example: Multi-language sitemap generation
215
+ */
216
+ export async function generateMultiLangSitemap() {
217
+ const products = await getProducts();
218
+ const locales = ['en', 'es', 'fr', 'de'];
219
+ const baseUrl = 'https://example.com';
220
+
221
+ const routes = products.map((product: any) => ({
222
+ url: `${baseUrl}/products/${product.slug}`,
223
+ lastModified: product.updatedAt,
224
+ changeFrequency: 'daily' as const,
225
+ priority: 0.8,
226
+ }));
227
+
228
+ return generateI18nSitemap(routes, locales, 'en');
229
+ }
230
+
231
+ /**
232
+ * Example: Preview mode metadata
233
+ */
234
+ export function DraftPageMetadata({ isDraft, page }: { isDraft: boolean; page: any }) {
235
+ const baseMetadata: Metadata = {
236
+ title: page.title,
237
+ description: page.description,
238
+ };
239
+
240
+ return generatePreviewMetadata(isDraft, baseMetadata, {
241
+ draftIndicator: '🚧 DRAFT',
242
+ noIndexDrafts: true,
243
+ });
244
+ }
245
+
246
+ /**
247
+ * Example: Async metadata generation with Next.js 15 patterns
248
+ */
249
+ export async function ProductPageMetadata(props: {
250
+ params: Promise<{ id: string }>;
251
+ searchParams: Promise<{ variant?: string }>;
252
+ }) {
253
+ return generateMetadataAsync({
254
+ params: props.params as Promise<Record<string, string>>,
255
+ searchParams: props.searchParams as Promise<Record<string, string | string[] | undefined>>,
256
+ generator: async (params, searchParams: any) => {
257
+ const product = await getProduct(params.id ?? '');
258
+ const { variant } = searchParams;
259
+
260
+ return {
261
+ title: variant ? `${product.name} - ${variant as string}` : product.name,
262
+ description: product.description,
263
+ };
264
+ },
265
+ });
266
+ }
267
+
268
+ /**
269
+ * Example: Edge-compatible metadata generation
270
+ */
271
+ export async function EdgeMetadata(request: any) {
272
+ // This runs on the edge runtime
273
+ const url = new URL(request.url);
274
+ const slug = url.pathname.split('/').pop();
275
+
276
+ return generateMetadataEdge(request, {
277
+ title: `Edge Page - ${slug}`,
278
+ description: 'This metadata was generated on the edge',
279
+ image: '/images/edge-og.jpg',
280
+ });
281
+ }
282
+
283
+ /**
284
+ * Example: Using SEOManager for consistent configuration
285
+ */
286
+ const seoManager = new SEOManager({
287
+ applicationName: 'My E-commerce Store',
288
+ author: {
289
+ name: 'John Doe',
290
+ url: 'https://johndoe.com',
291
+ },
292
+ keywords: ['ecommerce', 'shopping', 'online store'],
293
+ locale: 'en_US',
294
+ publisher: 'My Company',
295
+ themeColor: '#0070f3',
296
+ twitterHandle: '@mystore',
297
+ });
298
+
299
+ /**
300
+ * Example: Generate error page metadata
301
+ */
302
+ export function ErrorPageMetadata(statusCode: number) {
303
+ return seoManager.createErrorMetadata(statusCode);
304
+ }
305
+
306
+ /**
307
+ * Example: Generate metadata with SEOManager
308
+ */
309
+ export function PageWithSEOManager({ page }: { page: any }) {
310
+ return seoManager.createMetadata({
311
+ title: page.title,
312
+ description: page.description,
313
+ image: page.image,
314
+ keywords: page.tags,
315
+ article:
316
+ page.type === 'article'
317
+ ? {
318
+ publishedTime: page.publishedAt,
319
+ modifiedTime: page.updatedAt,
320
+ authors: [page.author],
321
+ tags: page.tags,
322
+ }
323
+ : undefined,
324
+ });
325
+ }
326
+
327
+ // ============================================
328
+ // HELPER FUNCTIONS (for examples)
329
+ // ============================================
330
+
331
+ async function getProduct(id: string) {
332
+ // Mock implementation
333
+ return {
334
+ id,
335
+ name: 'Example Product',
336
+ description: 'A great product',
337
+ price: 99.99,
338
+ currency: 'USD',
339
+ image: '/images/product.jpg',
340
+ inStock: true,
341
+ brand: 'Example Brand',
342
+
343
+ slug: 'example-product',
344
+ updatedAt: new Date(),
345
+ };
346
+ }
347
+
348
+ async function getProducts() {
349
+ // Mock implementation
350
+ return [
351
+ { slug: 'product-1', updatedAt: new Date() },
352
+ { slug: 'product-2', updatedAt: new Date() },
353
+ ];
354
+ }
355
+
356
+ /**
357
+ * Example: Complete page with all SEO features
358
+ */
359
+ export default async function ComprehensiveSEOExample() {
360
+ const product = await getProduct('123');
361
+
362
+ // Generate metadata
363
+ const metadata = metadataTemplates.product({
364
+ name: product.name,
365
+ description: product.description,
366
+ price: product.price,
367
+ currency: product.currency,
368
+ image: product.image,
369
+ availability: 'InStock',
370
+ });
371
+
372
+ // Return complete page setup
373
+ return {
374
+ metadata,
375
+ viewport: viewportPresets.default,
376
+ structuredData: {
377
+ '@context': 'https://schema.org',
378
+ '@type': 'Product',
379
+ name: product.name,
380
+ description: product.description,
381
+ },
382
+ };
383
+ }
@@ -0,0 +1,241 @@
1
+ /**
2
+ * @fileoverview Next.js 15 + next-sitemap Integration Example
3
+ * @module @repo/seo/examples/nextjs-15-integration
4
+ *
5
+ * This example demonstrates how to integrate Next.js 15's native sitemap
6
+ * functionality with next-sitemap for the best of both worlds.
7
+ *
8
+ * Benefits of this approach:
9
+ * - Use Next.js 15's app directory for dynamic sitemaps
10
+ * - Use next-sitemap for static generation and advanced features
11
+ * - Automatic sitemap index generation for large sites
12
+ * - Unified robots.txt generation
13
+ *
14
+ * Prerequisites:
15
+ * - Next.js 15.0.0 or later
16
+ * - next-sitemap 4.0.0 or later (optional)
17
+ * - @repo/seo package
18
+ *
19
+ * @example
20
+ * // 1. Create app directory sitemaps for dynamic content
21
+ * // 2. Use next-sitemap for static pages and sitemap index
22
+ * // 3. Combine both in robots.txt
23
+ */
24
+
25
+ // ============================================
26
+ // STEP 1: App Directory Dynamic Sitemaps
27
+ // ============================================
28
+
29
+ // ============================================
30
+ // STEP 5: Helper for Hybrid Approach
31
+ // ============================================
32
+ import { type MetadataRoute } from 'next';
33
+
34
+ // app/sitemaps/products/sitemap.ts
35
+ import { logError } from '@repo/shared/logs';
36
+
37
+ import {
38
+ convertToNextSitemap,
39
+ createIntegratedSitemapConfig,
40
+ generateSitemapObject,
41
+ type DynamicSitemapRoute,
42
+ } from '../server-next';
43
+
44
+ /**
45
+ * Dynamic product sitemap using Next.js 15 native support
46
+ * This will be available at /sitemaps/products/sitemap.xml
47
+ */
48
+ export async function generateProductSitemap(): Promise<MetadataRoute.Sitemap> {
49
+ const baseUrl = process.env.NEXT_PUBLIC_URL ?? 'https://example.com';
50
+
51
+ // Fetch your products
52
+ const products = (await fetch(`${baseUrl}/api/products`).then(res => res.json())) as Array<{
53
+ slug: string;
54
+ updatedAt: string;
55
+ name: string;
56
+ images?: Array<{ url: string; alt: string }>;
57
+ }>;
58
+
59
+ const routes: DynamicSitemapRoute[] = products.map(product => ({
60
+ url: `${baseUrl}/products/${product.slug}`,
61
+ lastModified: new Date(product.updatedAt),
62
+ changeFrequency: 'daily',
63
+ priority: 0.8,
64
+ // Next.js 15 supports images in sitemaps!
65
+ images: product.images?.map(img => ({
66
+ url: img.url,
67
+ title: img.alt,
68
+ caption: product.name,
69
+ })),
70
+ }));
71
+
72
+ return generateSitemapObject(routes);
73
+ }
74
+
75
+ // app/sitemaps/blog/sitemap.ts
76
+ /**
77
+ * Dynamic blog sitemap
78
+ * This will be available at /sitemaps/blog/sitemap.xml
79
+ */
80
+ export async function generateBlogSitemap(): Promise<MetadataRoute.Sitemap> {
81
+ const baseUrl = process.env.NEXT_PUBLIC_URL ?? 'https://example.com';
82
+
83
+ const posts = (await fetch(`${baseUrl}/api/posts`).then(res => res.json())) as Array<{
84
+ slug: string;
85
+ publishedAt: string;
86
+ }>;
87
+
88
+ const routes: DynamicSitemapRoute[] = posts.map(post => ({
89
+ url: `${baseUrl}/blog/${post.slug}`,
90
+ lastModified: new Date(post.publishedAt),
91
+ changeFrequency: 'weekly',
92
+ priority: 0.6,
93
+ }));
94
+
95
+ return generateSitemapObject(routes);
96
+ }
97
+
98
+ const config = createIntegratedSitemapConfig({
99
+ siteUrl: process.env.SITE_URL ?? 'https://example.com',
100
+ generateRobotsTxt: true,
101
+
102
+ // Tell next-sitemap about your app directory sitemaps
103
+ appDirSitemaps: ['/sitemaps/products/sitemap.xml', '/sitemaps/blog/sitemap.xml'],
104
+
105
+ // Handle static pages with next-sitemap
106
+ exclude: [
107
+ '/api/*',
108
+ '/admin/*',
109
+ // Exclude dynamic routes handled by app directory
110
+ '/products/*',
111
+ '/blog/*',
112
+ ],
113
+
114
+ // Merge additional static routes
115
+
116
+ additionalPaths: async (_config: unknown) => {
117
+ return [
118
+ { loc: '/', changefreq: 'daily', priority: 1 },
119
+ { loc: '/about', changefreq: 'monthly', priority: 0.8 },
120
+ { loc: '/contact', changefreq: 'monthly', priority: 0.7 },
121
+ ];
122
+ },
123
+
124
+ // Advanced: Transform function for static pages (must be synchronous)
125
+ transform: (_config: unknown, path: string) => {
126
+ // Custom logic for specific paths
127
+ if (path === '/') {
128
+ return {
129
+ loc: path,
130
+ changefreq: 'daily',
131
+ priority: 1,
132
+ lastmod: new Date().toISOString(),
133
+ };
134
+ }
135
+
136
+ return {
137
+ loc: path,
138
+ changefreq: 'monthly',
139
+ priority: 0.5,
140
+ lastmod: new Date().toISOString(),
141
+ };
142
+ },
143
+ });
144
+
145
+ export default config;
146
+
147
+ // ============================================
148
+ // STEP 3: Unified Robots.txt
149
+ // ============================================
150
+
151
+ // app/robots.ts
152
+ /**
153
+ * Unified robots.txt that references all sitemaps
154
+ * Both from next-sitemap and app directory
155
+ */
156
+ export function robots(): MetadataRoute.Robots {
157
+ const baseUrl = process.env.NEXT_PUBLIC_URL ?? 'https://example.com';
158
+
159
+ return {
160
+ rules: [
161
+ {
162
+ userAgent: '*',
163
+ allow: '/',
164
+ disallow: ['/api/', '/admin/', '/private/'],
165
+ },
166
+ {
167
+ userAgent: 'Googlebot',
168
+ allow: '/',
169
+ },
170
+ ],
171
+ // Reference all sitemaps
172
+ sitemap: [
173
+ `${baseUrl}/sitemap.xml`, // Main sitemap from next-sitemap
174
+ `${baseUrl}/sitemap-0.xml`, // Additional sitemaps from next-sitemap
175
+ `${baseUrl}/sitemaps/products/sitemap.xml`, // App directory sitemap
176
+ `${baseUrl}/sitemaps/blog/sitemap.xml`, // App directory sitemap
177
+ ],
178
+ };
179
+ }
180
+
181
+ // ============================================
182
+ // STEP 4: Sitemap Index (Optional)
183
+ // ============================================
184
+
185
+ // app/sitemap.ts
186
+ /**
187
+ * Main sitemap index that combines all sitemaps
188
+ * This creates a sitemap index for very large sites
189
+ */
190
+ export function sitemap(): MetadataRoute.Sitemap {
191
+ const baseUrl = process.env.NEXT_PUBLIC_URL ?? 'https://example.com';
192
+
193
+ // Return sitemap index entries
194
+ return [
195
+ {
196
+ url: `${baseUrl}/sitemap-static.xml`,
197
+ lastModified: new Date(),
198
+ },
199
+ {
200
+ url: `${baseUrl}/sitemaps/products/sitemap.xml`,
201
+ lastModified: new Date(),
202
+ },
203
+ {
204
+ url: `${baseUrl}/sitemaps/blog/sitemap.xml`,
205
+ lastModified: new Date(),
206
+ },
207
+ ];
208
+ }
209
+
210
+ /**
211
+ * Utility to fetch and merge sitemaps from different sources
212
+ */
213
+ export async function mergeSitemaps(sources: string[]): Promise<MetadataRoute.Sitemap> {
214
+ const allRoutes: MetadataRoute.Sitemap = [];
215
+
216
+ for (const source of sources) {
217
+ try {
218
+ const response = await fetch(source);
219
+ const data = (await response.json()) as MetadataRoute.Sitemap;
220
+ allRoutes.push(...data);
221
+ } catch (error: unknown) {
222
+ logError(`Failed to fetch sitemap from ${source}`, { error, source });
223
+ }
224
+ }
225
+
226
+ return allRoutes;
227
+ }
228
+
229
+ /**
230
+ * Convert between Next.js and next-sitemap formats
231
+ */
232
+ export function convertSitemapFormats(
233
+ nextjsSitemap: Array<{
234
+ url: string;
235
+ lastModified?: Date;
236
+ changeFrequency?: string;
237
+ priority?: number;
238
+ }>,
239
+ ) {
240
+ return convertToNextSitemap(nextjsSitemap);
241
+ }
package/src/index.ts ADDED
@@ -0,0 +1,87 @@
1
+ /**
2
+ * @fileoverview Main SEO Package Index
3
+ *
4
+ * Re-exports commonly used SEO functionality from various modules.
5
+ * This provides a convenient single import point for basic SEO needs.
6
+ *
7
+ * Features:
8
+ * - Structured data (JSON-LD) generation
9
+ * - Next.js metadata utilities
10
+ * - SEO validation utilities
11
+ * - i18n-enhanced metadata
12
+ *
13
+ * @module @repo/seo
14
+ *
15
+ * @example Basic usage with structured data
16
+ * ```tsx
17
+ * import { JsonLd, structuredData } from '@repo/seo';
18
+ *
19
+ * const article = structuredData.article({
20
+ * headline: 'Getting Started',
21
+ * author: 'John Doe',
22
+ * datePublished: '2024-01-01'
23
+ * });
24
+ *
25
+ * export default function Page() {
26
+ * return <JsonLd data={article} />;
27
+ * }
28
+ * ```
29
+ *
30
+ * @example With validation
31
+ * ```ts
32
+ * import { validateSchemaOrg, structuredData } from '@repo/seo';
33
+ *
34
+ * const org = structuredData.organization({
35
+ * name: 'Acme Corp',
36
+ * url: 'https://acme.com'
37
+ * });
38
+ *
39
+ * if (validateSchemaOrg(org)) {
40
+ * // Data is valid
41
+ * }
42
+ * ```
43
+ *
44
+ * @example Next.js metadata
45
+ * ```ts
46
+ * import { createMetadata } from '@repo/seo';
47
+ *
48
+ * export const metadata = createMetadata({
49
+ * title: 'Page Title',
50
+ * description: 'Page description',
51
+ * image: '/og-image.png'
52
+ * });
53
+ * ```
54
+ */
55
+
56
+ // Re-export client-side components
57
+ export { JsonLd } from './client';
58
+
59
+ // Re-export server-side utilities
60
+ export { createStructuredData, structuredData } from './server';
61
+
62
+ // Re-export structured data utilities
63
+ export {
64
+ JsonLd as StructuredDataJsonLd,
65
+ createStructuredData as createStructuredDataUtil,
66
+ structuredData as structuredDataHelpers,
67
+ } from './components/structured-data';
68
+
69
+ // Re-export metadata utilities
70
+ export { createMetadata } from './utils/metadata';
71
+
72
+ // Re-export validation utilities (for convenience)
73
+ export {
74
+ validateImages,
75
+ validateImagesDetailed,
76
+ validateMetadata,
77
+ validateOpenGraph,
78
+ validateSchemaOrg,
79
+ validateSchemaOrgDetailed,
80
+ validateUrl,
81
+ validateUrls,
82
+ validateUrlsDetailed,
83
+ } from './utils/validation';
84
+
85
+ // Re-export types
86
+ export type { StructuredDataType, Thing, WithContext } from './server';
87
+ export type { ValidationResult } from './utils/validation';