|
1 | 1 | import { MetadataRoute } from 'next'; |
2 | 2 | import { getBlogPosts } from '@/lib/api/blog'; |
| 3 | +import { getHackathons } from '@/lib/api/hackathons'; |
| 4 | +import { getCrowdfundingProjects } from '@/lib/api/project'; |
3 | 5 | import type { BlogPost } from '@/types/blog'; |
| 6 | +import type { Hackathon } from '@/types/hackathon/core'; |
| 7 | +import type { Crowdfunding } from '@/types/project'; |
4 | 8 |
|
| 9 | +// Constants |
| 10 | +const SITE_URL = 'https://boundlessfi.xyz'; |
| 11 | +const MAX_ITEMS = 100; |
| 12 | + |
| 13 | +/** |
| 14 | + * Generates the sitemap for the website |
| 15 | + * Includes static pages and dynamically fetched content |
| 16 | + */ |
5 | 17 | export default async function sitemap(): Promise<MetadataRoute.Sitemap> { |
6 | | - // Fetch real blog posts from API |
7 | | - let blogPosts: MetadataRoute.Sitemap = []; |
8 | | - try { |
9 | | - const response = await getBlogPosts({ |
10 | | - status: 'PUBLISHED', |
11 | | - limit: 1000, // Fetch all published posts |
12 | | - }); |
13 | | - blogPosts = response.data.map((post: BlogPost) => ({ |
14 | | - url: `https://boundlessfi.xyz/blog/${post.slug}`, |
15 | | - lastModified: new Date(post.updatedAt || post.publishedAt), |
16 | | - changeFrequency: 'monthly' as const, |
17 | | - priority: 0.7, |
18 | | - })); |
19 | | - } catch { |
20 | | - // Silently fail if fetch fails, return empty array |
21 | | - } |
| 18 | + const [blogPosts, hackathons, crowdfundingProjects] = await Promise.all([ |
| 19 | + fetchBlogPostsSitemap(), |
| 20 | + fetchHackathonsSitemap(), |
| 21 | + fetchCrowdfundingProjectsSitemap(), |
| 22 | + ]); |
22 | 23 |
|
23 | | - const baseUrl = 'https://boundlessfi.xyz'; |
24 | | - return [ |
| 24 | + const staticPages: MetadataRoute.Sitemap = [ |
25 | 25 | { |
26 | | - url: baseUrl, |
| 26 | + url: SITE_URL, |
27 | 27 | lastModified: new Date(), |
28 | 28 | changeFrequency: 'weekly', |
29 | 29 | priority: 1.0, |
30 | 30 | }, |
31 | 31 | { |
32 | | - url: `${baseUrl}/about`, |
| 32 | + url: `${SITE_URL}/about`, |
33 | 33 | lastModified: new Date(), |
34 | 34 | changeFrequency: 'monthly', |
35 | 35 | priority: 0.8, |
36 | 36 | }, |
37 | 37 | { |
38 | | - url: `${baseUrl}/blog`, |
| 38 | + url: `${SITE_URL}/blog`, |
39 | 39 | lastModified: new Date(), |
40 | 40 | changeFrequency: 'weekly', |
41 | 41 | priority: 0.9, |
42 | 42 | }, |
43 | 43 | { |
44 | | - url: `${baseUrl}/projects`, |
| 44 | + url: `${SITE_URL}/projects`, |
45 | 45 | lastModified: new Date(), |
46 | 46 | changeFrequency: 'weekly', |
47 | 47 | priority: 0.9, |
48 | 48 | }, |
49 | 49 | { |
50 | | - url: `${baseUrl}/me`, |
| 50 | + url: `${SITE_URL}/hackathons`, |
| 51 | + lastModified: new Date(), |
| 52 | + changeFrequency: 'weekly', |
| 53 | + priority: 0.9, |
| 54 | + }, |
| 55 | + { |
| 56 | + url: `${SITE_URL}/me`, |
51 | 57 | lastModified: new Date(), |
52 | 58 | changeFrequency: 'monthly', |
53 | 59 | priority: 0.6, |
54 | 60 | }, |
55 | | - ...blogPosts, |
56 | 61 | ]; |
| 62 | + |
| 63 | + return [...staticPages, ...blogPosts, ...hackathons, ...crowdfundingProjects]; |
| 64 | +} |
| 65 | + |
| 66 | +/** |
| 67 | + * Fetches published blog posts and formats them for sitemap |
| 68 | + * Returns empty array if fetch fails |
| 69 | + */ |
| 70 | +async function fetchBlogPostsSitemap(): Promise<MetadataRoute.Sitemap> { |
| 71 | + try { |
| 72 | + const response = await getBlogPosts({ |
| 73 | + status: 'PUBLISHED', |
| 74 | + limit: MAX_ITEMS, |
| 75 | + }); |
| 76 | + |
| 77 | + // Validate response |
| 78 | + if (!response?.data || !Array.isArray(response.data)) { |
| 79 | + return []; |
| 80 | + } |
| 81 | + |
| 82 | + return response.data |
| 83 | + .filter((post: BlogPost) => { |
| 84 | + // Validate required fields |
| 85 | + if (!post.slug) { |
| 86 | + return false; |
| 87 | + } |
| 88 | + return true; |
| 89 | + }) |
| 90 | + .map((post: BlogPost) => ({ |
| 91 | + url: `${SITE_URL}/blog/${post.slug}`, |
| 92 | + lastModified: new Date( |
| 93 | + post.updatedAt || post.publishedAt || new Date() |
| 94 | + ), |
| 95 | + changeFrequency: 'monthly' as const, |
| 96 | + priority: 0.7, |
| 97 | + })); |
| 98 | + } catch { |
| 99 | + // Silently fail if fetch fails, return empty array |
| 100 | + return []; |
| 101 | + } |
| 102 | +} |
| 103 | + |
| 104 | +/** |
| 105 | + * Fetches published hackathons and formats them for sitemap |
| 106 | + * Returns empty array if fetch fails |
| 107 | + */ |
| 108 | +async function fetchHackathonsSitemap(): Promise<MetadataRoute.Sitemap> { |
| 109 | + try { |
| 110 | + const response = await getHackathons(1, MAX_ITEMS, {}); |
| 111 | + |
| 112 | + // Validate response |
| 113 | + if ( |
| 114 | + !response?.data || |
| 115 | + !response.data.hackathons || |
| 116 | + !Array.isArray(response.data.hackathons) |
| 117 | + ) { |
| 118 | + return []; |
| 119 | + } |
| 120 | + |
| 121 | + return response.data.hackathons |
| 122 | + .filter((hackathon: Hackathon) => { |
| 123 | + // Validate required fields |
| 124 | + if (!hackathon.slug) { |
| 125 | + return false; |
| 126 | + } |
| 127 | + return true; |
| 128 | + }) |
| 129 | + .map((hackathon: Hackathon) => ({ |
| 130 | + url: `${SITE_URL}/hackathons/${hackathon.slug}`, |
| 131 | + lastModified: new Date( |
| 132 | + hackathon.updatedAt || hackathon.publishedAt || new Date() |
| 133 | + ), |
| 134 | + changeFrequency: 'weekly' as const, |
| 135 | + priority: 0.8, |
| 136 | + })); |
| 137 | + } catch { |
| 138 | + // Silently fail if fetch fails, return empty array |
| 139 | + return []; |
| 140 | + } |
| 141 | +} |
| 142 | + |
| 143 | +/** |
| 144 | + * Fetches crowdfunding projects and formats them for sitemap |
| 145 | + * Returns empty array if fetch fails |
| 146 | + */ |
| 147 | +async function fetchCrowdfundingProjectsSitemap(): Promise<MetadataRoute.Sitemap> { |
| 148 | + try { |
| 149 | + const response = await getCrowdfundingProjects(1, MAX_ITEMS); |
| 150 | + |
| 151 | + // Validate response |
| 152 | + if (!response?.data.campaigns || !Array.isArray(response.data.campaigns)) { |
| 153 | + return []; |
| 154 | + } |
| 155 | + |
| 156 | + return response.data.campaigns |
| 157 | + .filter((project: Crowdfunding) => { |
| 158 | + // Validate required fields |
| 159 | + if (!project.id) { |
| 160 | + return false; |
| 161 | + } |
| 162 | + return true; |
| 163 | + }) |
| 164 | + .map((project: Crowdfunding) => ({ |
| 165 | + url: `${SITE_URL}/projects/${project.id}`, |
| 166 | + lastModified: new Date(project.updatedAt || new Date()), |
| 167 | + changeFrequency: 'weekly' as const, |
| 168 | + priority: 0.8, |
| 169 | + })); |
| 170 | + } catch { |
| 171 | + // Silently fail if fetch fails, return empty array |
| 172 | + return []; |
| 173 | + } |
57 | 174 | } |
0 commit comments