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
194 lines
5.9 KiB
TypeScript
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,
|
|
]
|
|
}
|