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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 [] } }