Spaces:
Configuration error
Configuration error
perf: sitemap uses revalidate=3600 instead of force-dynamic (reduces DB load)
Browse files- src/app/sitemap.ts +12 -16
src/app/sitemap.ts
CHANGED
|
@@ -3,8 +3,7 @@ import type { PrismaClient } from '@prisma/client'
|
|
| 3 |
import { getAllToolsComplete } from '@/lib/tools/index'
|
| 4 |
|
| 5 |
/**
|
| 6 |
-
* Resolve the canonical site URL at runtime
|
| 7 |
-
* Fallback chain: SITE_URL β NEXT_PUBLIC_APP_URL β hardcoded prod URL.
|
| 8 |
*/
|
| 9 |
function getBaseUrl(): string {
|
| 10 |
return (
|
|
@@ -14,12 +13,11 @@ function getBaseUrl(): string {
|
|
| 14 |
)
|
| 15 |
}
|
| 16 |
|
| 17 |
-
//
|
| 18 |
-
export const
|
| 19 |
|
| 20 |
/**
|
| 21 |
-
* Safely obtain a Prisma client via dynamic import
|
| 22 |
-
* DATABASE_URL or cold-start failure never crashes the module.
|
| 23 |
*/
|
| 24 |
async function getPrisma(): Promise<PrismaClient | null> {
|
| 25 |
try {
|
|
@@ -31,18 +29,18 @@ async function getPrisma(): Promise<PrismaClient | null> {
|
|
| 31 |
}
|
| 32 |
}
|
| 33 |
|
| 34 |
-
//
|
| 35 |
const toolCategories = [
|
| 36 |
'prompting', 'marketing', 'branding', 'copywriting', 'business',
|
| 37 |
'email', 'product', 'hr', 'personal-brand', 'operations',
|
| 38 |
'social-media', 'education', 'development', 'creative', 'research',
|
| 39 |
]
|
| 40 |
|
| 41 |
-
//
|
| 42 |
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
| 43 |
const BASE_URL = getBaseUrl()
|
| 44 |
|
| 45 |
-
//
|
| 46 |
const staticPages: MetadataRoute.Sitemap = [
|
| 47 |
{ url: BASE_URL, lastModified: new Date(), changeFrequency: 'daily', priority: 1 },
|
| 48 |
{ url: `${BASE_URL}/explore`, lastModified: new Date(), changeFrequency: 'daily', priority: 0.9 },
|
|
@@ -68,7 +66,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
| 68 |
{ url: `${BASE_URL}/terms`, lastModified: new Date(), changeFrequency: 'yearly', priority: 0.3 },
|
| 69 |
]
|
| 70 |
|
| 71 |
-
//
|
| 72 |
const toolCategoryPages: MetadataRoute.Sitemap = toolCategories.map((category) => ({
|
| 73 |
url: `${BASE_URL}/tools/${category}`,
|
| 74 |
lastModified: new Date(),
|
|
@@ -76,7 +74,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
| 76 |
priority: 0.7,
|
| 77 |
}))
|
| 78 |
|
| 79 |
-
//
|
| 80 |
let toolPages: MetadataRoute.Sitemap = []
|
| 81 |
try {
|
| 82 |
const allTools = getAllToolsComplete()
|
|
@@ -90,7 +88,6 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
| 90 |
console.warn('Sitemap: could not load tools list')
|
| 91 |
}
|
| 92 |
|
| 93 |
-
// Always start with pages that don't require the database
|
| 94 |
const pages: MetadataRoute.Sitemap = [...staticPages, ...toolCategoryPages, ...toolPages]
|
| 95 |
|
| 96 |
const prisma = await getPrisma()
|
|
@@ -99,7 +96,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
| 99 |
return pages
|
| 100 |
}
|
| 101 |
|
| 102 |
-
//
|
| 103 |
const [promptPages, creatorPages, collectionPages, imagePromptPages, characterPages, workflowPages] =
|
| 104 |
await Promise.all([
|
| 105 |
fetchPromptPages(prisma, BASE_URL),
|
|
@@ -121,7 +118,7 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
| 121 |
]
|
| 122 |
}
|
| 123 |
|
| 124 |
-
//
|
| 125 |
|
| 126 |
async function fetchPromptPages(prisma: PrismaClient, baseUrl: string): Promise<MetadataRoute.Sitemap> {
|
| 127 |
try {
|
|
@@ -129,7 +126,7 @@ async function fetchPromptPages(prisma: PrismaClient, baseUrl: string): Promise<
|
|
| 129 |
where: { visibility: 'public' },
|
| 130 |
select: { slug: true, updatedAt: true },
|
| 131 |
orderBy: { totalRuns: 'desc' },
|
| 132 |
-
take: 5000,
|
| 133 |
})
|
| 134 |
return prompts.map((prompt) => ({
|
| 135 |
url: `${baseUrl}/p/${prompt.slug}`,
|
|
@@ -156,7 +153,6 @@ async function fetchCreatorPages(prisma: PrismaClient, baseUrl: string): Promise
|
|
| 156 |
return creators
|
| 157 |
.filter((c) => c.username)
|
| 158 |
.map((creator) => ({
|
| 159 |
-
// β
Use username slug (not opaque ID) for SEO
|
| 160 |
url: `${baseUrl}/creator/${creator.username}`,
|
| 161 |
lastModified: creator.updatedAt,
|
| 162 |
changeFrequency: 'weekly' as const,
|
|
|
|
| 3 |
import { getAllToolsComplete } from '@/lib/tools/index'
|
| 4 |
|
| 5 |
/**
|
| 6 |
+
* Resolve the canonical site URL at runtime.
|
|
|
|
| 7 |
*/
|
| 8 |
function getBaseUrl(): string {
|
| 9 |
return (
|
|
|
|
| 13 |
)
|
| 14 |
}
|
| 15 |
|
| 16 |
+
// Revalidate sitemap every hour instead of regenerating on every request
|
| 17 |
+
export const revalidate = 3600
|
| 18 |
|
| 19 |
/**
|
| 20 |
+
* Safely obtain a Prisma client via dynamic import.
|
|
|
|
| 21 |
*/
|
| 22 |
async function getPrisma(): Promise<PrismaClient | null> {
|
| 23 |
try {
|
|
|
|
| 29 |
}
|
| 30 |
}
|
| 31 |
|
| 32 |
+
// Tool categories (static list)
|
| 33 |
const toolCategories = [
|
| 34 |
'prompting', 'marketing', 'branding', 'copywriting', 'business',
|
| 35 |
'email', 'product', 'hr', 'personal-brand', 'operations',
|
| 36 |
'social-media', 'education', 'development', 'creative', 'research',
|
| 37 |
]
|
| 38 |
|
| 39 |
+
// Main sitemap function
|
| 40 |
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
| 41 |
const BASE_URL = getBaseUrl()
|
| 42 |
|
| 43 |
+
// 1. Static pages
|
| 44 |
const staticPages: MetadataRoute.Sitemap = [
|
| 45 |
{ url: BASE_URL, lastModified: new Date(), changeFrequency: 'daily', priority: 1 },
|
| 46 |
{ url: `${BASE_URL}/explore`, lastModified: new Date(), changeFrequency: 'daily', priority: 0.9 },
|
|
|
|
| 66 |
{ url: `${BASE_URL}/terms`, lastModified: new Date(), changeFrequency: 'yearly', priority: 0.3 },
|
| 67 |
]
|
| 68 |
|
| 69 |
+
// 2. Tool category pages
|
| 70 |
const toolCategoryPages: MetadataRoute.Sitemap = toolCategories.map((category) => ({
|
| 71 |
url: `${BASE_URL}/tools/${category}`,
|
| 72 |
lastModified: new Date(),
|
|
|
|
| 74 |
priority: 0.7,
|
| 75 |
}))
|
| 76 |
|
| 77 |
+
// 3. Individual tool pages (177+ tools)
|
| 78 |
let toolPages: MetadataRoute.Sitemap = []
|
| 79 |
try {
|
| 80 |
const allTools = getAllToolsComplete()
|
|
|
|
| 88 |
console.warn('Sitemap: could not load tools list')
|
| 89 |
}
|
| 90 |
|
|
|
|
| 91 |
const pages: MetadataRoute.Sitemap = [...staticPages, ...toolCategoryPages, ...toolPages]
|
| 92 |
|
| 93 |
const prisma = await getPrisma()
|
|
|
|
| 96 |
return pages
|
| 97 |
}
|
| 98 |
|
| 99 |
+
// 4. Dynamic DB pages
|
| 100 |
const [promptPages, creatorPages, collectionPages, imagePromptPages, characterPages, workflowPages] =
|
| 101 |
await Promise.all([
|
| 102 |
fetchPromptPages(prisma, BASE_URL),
|
|
|
|
| 118 |
]
|
| 119 |
}
|
| 120 |
|
| 121 |
+
// Helpers
|
| 122 |
|
| 123 |
async function fetchPromptPages(prisma: PrismaClient, baseUrl: string): Promise<MetadataRoute.Sitemap> {
|
| 124 |
try {
|
|
|
|
| 126 |
where: { visibility: 'public' },
|
| 127 |
select: { slug: true, updatedAt: true },
|
| 128 |
orderBy: { totalRuns: 'desc' },
|
| 129 |
+
take: 5000,
|
| 130 |
})
|
| 131 |
return prompts.map((prompt) => ({
|
| 132 |
url: `${baseUrl}/p/${prompt.slug}`,
|
|
|
|
| 153 |
return creators
|
| 154 |
.filter((c) => c.username)
|
| 155 |
.map((creator) => ({
|
|
|
|
| 156 |
url: `${baseUrl}/creator/${creator.username}`,
|
| 157 |
lastModified: creator.updatedAt,
|
| 158 |
changeFrequency: 'weekly' as const,
|