SPB Git forge

spb/countryatlas

Public
20commits 1branches 0releases
268.3 MBsize
maindefault branch
12 days agolast push
TypeScript 57% Python 38.6% JavaScript 3.6% CSS 0.6%
3.5 KB · 77 lines typescript
Raw Blame History
1import type { MetadataRoute } from 'next';2import { api, safe } from '@/lib/api';3import { apiCompare } from '@/lib/api-compare';4import { apiExplore } from '@/lib/api-explore';5import { SITE_URL, routes } from '@/lib/site';6import { STORIES } from '@/lib/stories';7import { TOPICS } from '@/lib/topics';89/**10 * Sharded sitemap (Next `generateSitemaps`): one file per URL family so no shard grows past a few thousand URLs.11 * Emitted at /sitemap/<id>.xml; robots.ts lists every shard. Parameterised analytical views (/explore?…,12 * /scatter?…, /finder?…) are deliberately absent: they are noindex and their canonical is the bare path.13 */14export const SITEMAP_IDS = ['core', 'countries', 'country-topics', 'indicators', 'rankings', 'regions', 'stories'] as const;15export type SitemapId = (typeof SITEMAP_IDS)[number];1617export async function generateSitemaps(): Promise<Array<{ id: SitemapId }>> {18  return SITEMAP_IDS.map((id) => ({ id }));19}2021export default async function sitemap(props: { id: Promise<string> | string }): Promise<MetadataRoute.Sitemap> {22  const id = (await props.id) as SitemapId;23  const health = await safe(api.health());24  const lastModified = health?.built_at ? new Date(health.built_at) : new Date();25  const entry = (path: string, changeFrequency: MetadataRoute.Sitemap[number]['changeFrequency'], priority: number): MetadataRoute.Sitemap[number] => ({ url: `${SITE_URL}${path}`, lastModified, changeFrequency, priority });2627  switch (id) {28    case 'core':29      return [30        entry('/', 'daily', 1),31        entry(routes.explore(), 'daily', 0.9),32        entry(routes.countries(), 'daily', 0.9),33        entry(routes.compare(), 'monthly', 0.7),34        entry(routes.rankings(), 'daily', 0.9),35        entry(routes.indicators(), 'daily', 0.9),36        entry(routes.changes(), 'daily', 0.6),37        entry(routes.regions(), 'weekly', 0.7),38        entry(routes.trajectories(), 'monthly', 0.6),39        entry(routes.scatter(), 'monthly', 0.6),40        entry(routes.finder(), 'monthly', 0.6),41        entry(routes.extremes(), 'weekly', 0.6),42        entry(routes.peers(), 'monthly', 0.5),43        entry(routes.stories(), 'weekly', 0.7),44        entry(routes.download(), 'monthly', 0.5),45        entry(routes.updates(), 'daily', 0.4),46        entry(routes.sources(), 'weekly', 0.5),47        entry(routes.methodology(), 'monthly', 0.5),48        entry(routes.api(), 'monthly', 0.5),49        ...((await safe(apiExplore.sources()))?.items ?? []).map((s) => entry(routes.source(s.id), 'weekly', 0.4)),50      ];51    case 'countries': {52      const res = await safe(api.countries());53      return (res?.items ?? []).map((c) => entry(routes.country(c.slug ?? c.id), 'weekly', 0.8));54    }55    case 'country-topics': {56      const res = await safe(api.countries());57      return (res?.items ?? []).flatMap((c) => TOPICS.map((tp) => entry(routes.countryTopic(c.slug ?? c.id, tp.id), 'weekly', 0.6)));58    }59    case 'indicators': {60      const res = await safe(apiExplore.indicators());61      return (res?.items ?? []).map((i) => entry(routes.indicator(i.slug), 'weekly', 0.7));62    }63    case 'rankings': {64      const res = await safe(apiCompare.rankings());65      return (res?.items ?? []).map((i) => entry(routes.ranking(i.slug), 'weekly', 0.7));66    }67    case 'regions': {68      const res = await safe(apiExplore.regions());69      return (res?.items ?? []).map((g) => entry(routes.region(g.slug ?? g.id), 'weekly', 0.6));70    }71    case 'stories':72      return STORIES.map((s) => entry(routes.story(s.slug), 'weekly', 0.7));73    default:74      return [];75  }76}77