Files
simstudioai--sim/apps/sim/app/sitemap.ts
T
wehub-resource-sync d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

194 lines
5.9 KiB
TypeScript

import type { MetadataRoute } from 'next'
import { getAllPostMeta as getAllBlogPostMeta } from '@/lib/blog/registry'
import type { ContentMeta } from '@/lib/content/schema'
import { latestModified } from '@/lib/content/utils'
import { SITE_URL } from '@/lib/core/utils/urls'
import { INTEGRATIONS, INTEGRATIONS_UPDATED_AT } from '@/lib/integrations'
import { getAllPostMeta as getAllLibraryPostMeta } from '@/lib/library/registry'
import {
ALL_COMPETITORS,
getLatestVerifiedDate,
SIM_LATEST_VERIFIED,
} from '@/app/(landing)/comparison/utils'
import { ALL_CATALOG_MODELS, MODEL_PROVIDERS_WITH_CATALOGS } from '@/app/(landing)/models/utils'
/** One sitemap entry per author, timestamped by their most recently updated post. */
function buildAuthorPages(posts: ContentMeta[], basePath: string): MetadataRoute.Sitemap {
const authorsMap = new Map<string, Date>()
for (const p of posts) {
for (const author of p.authors ?? [p.author]) {
const postDate = new Date(p.updated ?? p.date)
const existing = authorsMap.get(author.id)
if (!existing || postDate > existing) {
authorsMap.set(author.id, postDate)
}
}
}
return [...authorsMap.entries()].map(([id, date]) => ({
url: `${SITE_URL}${basePath}/authors/${id}`,
lastModified: date,
}))
}
/**
* Generate the public sitemap by composing static landing pages with the
* dynamic catalogs (blog posts, library posts, authors, integrations, model
* providers, and individual models). Per-integration entries are emitted
* under `/integrations/{slug}` to match the landing route at
* `app/(landing)/integrations/[slug]`; slugs are guaranteed unique
* by the catalog generator in `scripts/generate-docs.ts`.
*/
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = SITE_URL
const [posts, libraryPosts] = await Promise.all([getAllBlogPostMeta(), getAllLibraryPostMeta()])
const latestPostDateValue = latestModified(posts)
const latestLibraryPostDate = latestModified(libraryPosts)
const modelTimes = MODEL_PROVIDERS_WITH_CATALOGS.flatMap((provider) =>
provider.models.map((model) => new Date(model.pricing.updatedAt).getTime())
)
const latestModelDate = modelTimes.length > 0 ? new Date(Math.max(...modelTimes)) : undefined
const integrationsUpdatedAt = new Date(`${INTEGRATIONS_UPDATED_AT}T00:00:00Z`)
const staticPages: MetadataRoute.Sitemap = [
{
url: baseUrl,
},
{
url: `${baseUrl}/workflows`,
},
{
url: `${baseUrl}/pricing`,
},
{
url: `${baseUrl}/demo`,
},
{
url: `${baseUrl}/contact`,
},
{
url: `${baseUrl}/careers`,
},
{
url: `${baseUrl}/enterprise`,
},
{
url: `${baseUrl}/solutions/compliance`,
},
{
url: `${baseUrl}/solutions/engineering`,
},
{
url: `${baseUrl}/solutions/finance`,
},
{
url: `${baseUrl}/solutions/hr`,
},
{
url: `${baseUrl}/solutions/it`,
},
{
url: `${baseUrl}/blog`,
lastModified: latestPostDateValue,
},
{
url: `${baseUrl}/blog/tags`,
lastModified: latestPostDateValue,
},
{
url: `${baseUrl}/library`,
lastModified: latestLibraryPostDate,
},
{
url: `${baseUrl}/library/tags`,
lastModified: latestLibraryPostDate,
},
{
url: `${baseUrl}/changelog`,
lastModified: latestPostDateValue,
},
{
url: `${baseUrl}/integrations`,
lastModified: integrationsUpdatedAt,
},
{
url: `${baseUrl}/models`,
lastModified: latestModelDate,
},
{
url: `${baseUrl}/terms`,
lastModified: new Date('2024-10-14'),
},
{
url: `${baseUrl}/privacy`,
lastModified: new Date('2024-10-14'),
},
]
const blogPages: MetadataRoute.Sitemap = posts.map((p) => ({
url: p.canonical,
lastModified: new Date(p.updated ?? p.date),
}))
const authorPages = buildAuthorPages(posts, '/blog')
const libraryPages: MetadataRoute.Sitemap = libraryPosts.map((p) => ({
url: p.canonical,
lastModified: new Date(p.updated ?? p.date),
}))
const libraryAuthorPages = buildAuthorPages(libraryPosts, '/library')
const integrationPages: MetadataRoute.Sitemap = INTEGRATIONS.map((integration) => ({
url: `${baseUrl}/integrations/${integration.slug}`,
lastModified: integrationsUpdatedAt,
}))
const providerPages: MetadataRoute.Sitemap = MODEL_PROVIDERS_WITH_CATALOGS.flatMap((provider) => {
if (provider.models.length === 0) return []
return [
{
url: `${baseUrl}${provider.href}`,
lastModified: new Date(
Math.max(...provider.models.map((model) => new Date(model.pricing.updatedAt).getTime()))
),
},
]
})
const modelEntries: MetadataRoute.Sitemap = ALL_CATALOG_MODELS.map((model) => ({
url: `${baseUrl}${model.href}`,
lastModified: new Date(model.pricing.updatedAt),
}))
// Matches the max(Sim, competitor) verified-date logic each detail page's own
// JSON-LD `dateModified` uses, so the sitemap timestamp never lags behind it.
const competitorLastModified = (competitor: (typeof ALL_COMPETITORS)[number]) =>
new Date(Math.max(SIM_LATEST_VERIFIED.getTime(), getLatestVerifiedDate(competitor).getTime()))
const comparisonLastModified =
ALL_COMPETITORS.length > 0
? new Date(Math.max(...ALL_COMPETITORS.map((c) => competitorLastModified(c).getTime())))
: SIM_LATEST_VERIFIED
const comparisonPages: MetadataRoute.Sitemap = [
{ url: `${baseUrl}/comparison`, lastModified: comparisonLastModified },
...ALL_COMPETITORS.map((competitor) => ({
url: `${baseUrl}/comparison/${competitor.id}`,
lastModified: competitorLastModified(competitor),
})),
]
return [
...staticPages,
...blogPages,
...authorPages,
...libraryPages,
...libraryAuthorPages,
...integrationPages,
...providerPages,
...modelEntries,
...comparisonPages,
]
}