Spaces:
Configuration error
Configuration error
| import { MetadataRoute } from 'next' | |
| import type { PrismaClient } from '@prisma/client' | |
| import { getAllToolsComplete } from '@/lib/tools/index' | |
| /** | |
| * Resolve the canonical site URL at runtime. | |
| */ | |
| function getBaseUrl(): string { | |
| return ( | |
| process.env.SITE_URL || | |
| process.env.NEXT_PUBLIC_APP_URL || | |
| 'https://open-prompt.netlify.app' | |
| ) | |
| } | |
| // Revalidate sitemap every hour instead of regenerating on every request | |
| export const revalidate = 3600 | |
| /** | |
| * Safely obtain a Prisma client via dynamic import. | |
| */ | |
| async function getPrisma(): Promise<PrismaClient | null> { | |
| try { | |
| const { prisma } = await import('@/lib/prisma') | |
| return prisma | |
| } catch (error) { | |
| console.error('Sitemap: failed to initialise Prisma client:', error) | |
| return null | |
| } | |
| } | |
| // Tool categories (static list) | |
| const toolCategories = [ | |
| 'prompting', 'marketing', 'branding', 'copywriting', 'business', | |
| 'email', 'product', 'hr', 'personal-brand', 'operations', | |
| 'social-media', 'education', 'development', 'creative', 'research', | |
| ] | |
| // Main sitemap function | |
| export default async function sitemap(): Promise<MetadataRoute.Sitemap> { | |
| const BASE_URL = getBaseUrl() | |
| // 1. Static pages | |
| const staticPages: MetadataRoute.Sitemap = [ | |
| { url: BASE_URL, lastModified: new Date(), changeFrequency: 'daily', priority: 1 }, | |
| { url: `${BASE_URL}/explore`, lastModified: new Date(), changeFrequency: 'daily', priority: 0.9 }, | |
| { url: `${BASE_URL}/tools`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.9 }, | |
| { url: `${BASE_URL}/thunderdome`, lastModified: new Date(), changeFrequency: 'daily', priority: 0.8 }, | |
| { url: `${BASE_URL}/workflows`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.8 }, | |
| { url: `${BASE_URL}/frameworks`, lastModified: new Date(), changeFrequency: 'monthly', priority: 0.7 }, | |
| { url: `${BASE_URL}/leaderboard`, lastModified: new Date(), changeFrequency: 'daily', priority: 0.7 }, | |
| { url: `${BASE_URL}/image-prompts`, lastModified: new Date(), changeFrequency: 'daily', priority: 0.8 }, | |
| { url: `${BASE_URL}/characters`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.7 }, | |
| { url: `${BASE_URL}/categories`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.7 }, | |
| { url: `${BASE_URL}/creators`, lastModified: new Date(), changeFrequency: 'daily', priority: 0.7 }, | |
| { url: `${BASE_URL}/collections`, lastModified: new Date(), changeFrequency: 'daily', priority: 0.6 }, | |
| { url: `${BASE_URL}/extension`, lastModified: new Date(), changeFrequency: 'monthly', priority: 0.6 }, | |
| { url: `${BASE_URL}/about`, lastModified: new Date(), changeFrequency: 'monthly', priority: 0.5 }, | |
| { url: `${BASE_URL}/pricing`, lastModified: new Date(), changeFrequency: 'monthly', priority: 0.7 }, | |
| { url: `${BASE_URL}/blog`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.6 }, | |
| { url: `${BASE_URL}/docs`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.6 }, | |
| { url: `${BASE_URL}/guides`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.6 }, | |
| { url: `${BASE_URL}/forum`, lastModified: new Date(), changeFrequency: 'daily', priority: 0.6 }, | |
| { url: `${BASE_URL}/workflow-library`, lastModified: new Date(), changeFrequency: 'weekly', priority: 0.6 }, | |
| { url: `${BASE_URL}/privacy`, lastModified: new Date(), changeFrequency: 'yearly', priority: 0.3 }, | |
| { url: `${BASE_URL}/terms`, lastModified: new Date(), changeFrequency: 'yearly', priority: 0.3 }, | |
| ] | |
| // 2. Tool category pages | |
| const toolCategoryPages: MetadataRoute.Sitemap = toolCategories.map((category) => ({ | |
| url: `${BASE_URL}/tools/${category}`, | |
| lastModified: new Date(), | |
| changeFrequency: 'weekly' as const, | |
| priority: 0.7, | |
| })) | |
| // 3. Individual tool pages (177+ tools) | |
| let toolPages: MetadataRoute.Sitemap = [] | |
| try { | |
| const allTools = getAllToolsComplete() | |
| toolPages = allTools.map((tool) => ({ | |
| url: `${BASE_URL}/tools/${tool.slug}`, | |
| lastModified: new Date(), | |
| changeFrequency: 'monthly' as const, | |
| priority: 0.65, | |
| })) | |
| } catch { | |
| console.warn('Sitemap: could not load tools list') | |
| } | |
| const pages: MetadataRoute.Sitemap = [...staticPages, ...toolCategoryPages, ...toolPages] | |
| const prisma = await getPrisma() | |
| if (!prisma) { | |
| console.warn('Sitemap: returning static-only sitemap (no DB)') | |
| return pages | |
| } | |
| // 4. Dynamic DB pages | |
| const [promptPages, creatorPages, collectionPages, imagePromptPages, characterPages, workflowPages] = | |
| await Promise.all([ | |
| fetchPromptPages(prisma, BASE_URL), | |
| fetchCreatorPages(prisma, BASE_URL), | |
| fetchCollectionPages(prisma, BASE_URL), | |
| fetchImagePromptPages(prisma, BASE_URL), | |
| fetchCharacterPages(prisma, BASE_URL), | |
| fetchWorkflowPages(prisma, BASE_URL), | |
| ]) | |
| return [ | |
| ...pages, | |
| ...promptPages, | |
| ...creatorPages, | |
| ...collectionPages, | |
| ...imagePromptPages, | |
| ...characterPages, | |
| ...workflowPages, | |
| ] | |
| } | |
| // Helpers | |
| async function fetchPromptPages(prisma: PrismaClient, baseUrl: string): Promise<MetadataRoute.Sitemap> { | |
| try { | |
| const prompts = await prisma.prompt.findMany({ | |
| where: { visibility: 'public' }, | |
| select: { slug: true, updatedAt: true }, | |
| orderBy: { totalRuns: 'desc' }, | |
| take: 5000, | |
| }) | |
| return prompts.map((prompt) => ({ | |
| url: `${baseUrl}/p/${prompt.slug}`, | |
| lastModified: prompt.updatedAt, | |
| changeFrequency: 'weekly' as const, | |
| priority: 0.8, | |
| })) | |
| } catch (error) { | |
| console.error('Sitemap: error fetching prompts:', error) | |
| return [] | |
| } | |
| } | |
| async function fetchCreatorPages(prisma: PrismaClient, baseUrl: string): Promise<MetadataRoute.Sitemap> { | |
| try { | |
| const creators = await prisma.user.findMany({ | |
| where: { | |
| username: { not: null }, | |
| prompts: { some: { visibility: 'public' } }, | |
| }, | |
| select: { username: true, updatedAt: true }, | |
| take: 1000, | |
| }) | |
| return creators | |
| .filter((c) => c.username) | |
| .map((creator) => ({ | |
| url: `${baseUrl}/creator/${creator.username}`, | |
| lastModified: creator.updatedAt, | |
| changeFrequency: 'weekly' as const, | |
| priority: 0.65, | |
| })) | |
| } catch (error) { | |
| console.error('Sitemap: error fetching creators:', error) | |
| return [] | |
| } | |
| } | |
| async function fetchCollectionPages(prisma: PrismaClient, baseUrl: string): Promise<MetadataRoute.Sitemap> { | |
| try { | |
| const collections = await prisma.collection.findMany({ | |
| where: { isPublic: true }, | |
| select: { id: true, updatedAt: true }, | |
| take: 1000, | |
| }) | |
| return collections.map((collection) => ({ | |
| url: `${baseUrl}/collections/${collection.id}`, | |
| lastModified: collection.updatedAt, | |
| changeFrequency: 'weekly' as const, | |
| priority: 0.5, | |
| })) | |
| } catch (error) { | |
| console.error('Sitemap: error fetching collections:', error) | |
| return [] | |
| } | |
| } | |
| async function fetchImagePromptPages(prisma: PrismaClient, baseUrl: string): Promise<MetadataRoute.Sitemap> { | |
| try { | |
| const imagePrompts = await prisma.imagePrompt.findMany({ | |
| where: { visibility: 'public' }, | |
| select: { slug: true, updatedAt: true }, | |
| orderBy: { totalUses: 'desc' }, | |
| take: 2000, | |
| }) | |
| return imagePrompts.map((p) => ({ | |
| url: `${baseUrl}/image-prompts/${p.slug}`, | |
| lastModified: p.updatedAt, | |
| changeFrequency: 'weekly' as const, | |
| priority: 0.7, | |
| })) | |
| } catch (error) { | |
| console.error('Sitemap: error fetching image prompts:', error) | |
| return [] | |
| } | |
| } | |
| async function fetchCharacterPages(prisma: PrismaClient, baseUrl: string): Promise<MetadataRoute.Sitemap> { | |
| try { | |
| const characters = await prisma.character.findMany({ | |
| where: { visibility: 'public' }, | |
| select: { slug: true, updatedAt: true }, | |
| orderBy: { totalChats: 'desc' }, | |
| take: 1000, | |
| }) | |
| return characters.map((c) => ({ | |
| url: `${baseUrl}/characters/${c.slug}`, | |
| lastModified: c.updatedAt, | |
| changeFrequency: 'weekly' as const, | |
| priority: 0.65, | |
| })) | |
| } catch (error) { | |
| console.error('Sitemap: error fetching characters:', error) | |
| return [] | |
| } | |
| } | |
| async function fetchWorkflowPages(prisma: PrismaClient, baseUrl: string): Promise<MetadataRoute.Sitemap> { | |
| try { | |
| const workflows = await prisma.workflow.findMany({ | |
| where: { visibility: 'public' }, | |
| select: { slug: true, updatedAt: true }, | |
| orderBy: { totalRuns: 'desc' }, | |
| take: 500, | |
| }) | |
| return workflows.map((w) => ({ | |
| url: `${baseUrl}/workflows/${w.slug}`, | |
| lastModified: w.updatedAt, | |
| changeFrequency: 'weekly' as const, | |
| priority: 0.6, | |
| })) | |
| } catch (error) { | |
| console.error('Sitemap: error fetching workflows:', error) | |
| return [] | |
| } | |
| } | |