From 07e0f237b984968ae0050d2d271792b5e365f81c Mon Sep 17 00:00:00 2001 From: Marc Mintel Date: Fri, 6 Feb 2026 17:01:25 +0100 Subject: [PATCH] feat: Introduce metadata-only retrieval functions for posts, products, and pages to optimize sitemap generation. --- app/sitemap.ts | 29 ++++++++++-------- lib/blog.ts | 40 ++++++++++++++++++++---- lib/mdx.ts | 82 +++++++++++++++++++++++++++++++++++++++++++++----- lib/pages.ts | 31 +++++++++++++++---- 4 files changed, 151 insertions(+), 31 deletions(-) diff --git a/app/sitemap.ts b/app/sitemap.ts index 7f3ff40b..64cac6ff 100644 --- a/app/sitemap.ts +++ b/app/sitemap.ts @@ -1,8 +1,10 @@ import { config } from '@/lib/config'; import { MetadataRoute } from 'next'; -import { getAllProducts } from '@/lib/mdx'; -import { getAllPosts } from '@/lib/blog'; -import { getAllPages } from '@/lib/pages'; +import { getAllProductsMetadata } from '@/lib/mdx'; +import { getAllPostsMetadata } from '@/lib/blog'; +import { getAllPagesMetadata } from '@/lib/pages'; + +export const revalidate = 3600; // Revalidate every hour export default async function sitemap(): Promise { const baseUrl = config.baseUrl || 'https://klz-cables.com'; @@ -34,11 +36,10 @@ export default async function sitemap(): Promise { } // Products - const products = await getAllProducts(locale); - for (const product of products) { - // We need to find the category for the product to build the URL - // In this project, products are under /products/[category]/[slug] - // The category is in product.frontmatter.categories + const productsMetadata = await getAllProductsMetadata(locale); + for (const product of productsMetadata) { + if (!product.frontmatter || !product.slug) continue; + const category = product.frontmatter.categories[0]?.toLowerCase().replace(/\s+/g, '-') || 'other'; sitemapEntries.push({ @@ -50,8 +51,10 @@ export default async function sitemap(): Promise { } // Blog posts - const posts = await getAllPosts(locale); - for (const post of posts) { + const postsMetadata = await getAllPostsMetadata(locale); + for (const post of postsMetadata) { + if (!post.frontmatter || !post.slug) continue; + sitemapEntries.push({ url: `${baseUrl}/${locale}/blog/${post.slug}`, lastModified: new Date(post.frontmatter.date), @@ -61,8 +64,10 @@ export default async function sitemap(): Promise { } // Static pages - const pages = await getAllPages(locale); - for (const page of pages) { + const pagesMetadata = await getAllPagesMetadata(locale); + for (const page of pagesMetadata) { + if (!page.slug) continue; + sitemapEntries.push({ url: `${baseUrl}/${locale}/${page.slug}`, lastModified: new Date(), diff --git a/lib/blog.ts b/lib/blog.ts index f79e4a33..85769873 100644 --- a/lib/blog.ts +++ b/lib/blog.ts @@ -41,11 +41,11 @@ export async function getPostBySlug(slug: string, locale: string): Promise { const postsDir = path.join(process.cwd(), 'data', 'blog', locale); if (!fs.existsSync(postsDir)) return []; - + const files = fs.readdirSync(postsDir); const posts = files - .filter(file => file.endsWith('.mdx')) - .map(file => { + .filter((file) => file.endsWith('.mdx')) + .map((file) => { const filePath = path.join(postsDir, file); const fileContent = fs.readFileSync(filePath, 'utf8'); const { data, content } = matter(fileContent); @@ -55,14 +55,42 @@ export async function getAllPosts(locale: string): Promise { content, }; }) - .sort((a, b) => new Date(b.frontmatter.date).getTime() - new Date(a.frontmatter.date).getTime()); + .sort( + (a, b) => new Date(b.frontmatter.date).getTime() - new Date(a.frontmatter.date).getTime(), + ); return posts; } -export async function getAdjacentPosts(slug: string, locale: string): Promise<{ prev: PostMdx | null; next: PostMdx | null }> { +export async function getAllPostsMetadata(locale: string): Promise[]> { + const postsDir = path.join(process.cwd(), 'data', 'blog', locale); + if (!fs.existsSync(postsDir)) return []; + + const files = fs.readdirSync(postsDir); + return files + .filter((file) => file.endsWith('.mdx')) + .map((file) => { + const filePath = path.join(postsDir, file); + const fileContent = fs.readFileSync(filePath, 'utf8'); + const { data } = matter(fileContent); + return { + slug: file.replace(/\.mdx$/, ''), + frontmatter: data as PostFrontmatter, + }; + }) + .sort( + (a, b) => + new Date(b.frontmatter.date as string).getTime() - + new Date(a.frontmatter.date as string).getTime(), + ); +} + +export async function getAdjacentPosts( + slug: string, + locale: string, +): Promise<{ prev: PostMdx | null; next: PostMdx | null }> { const posts = await getAllPosts(locale); - const currentIndex = posts.findIndex(post => post.slug === slug); + const currentIndex = posts.findIndex((post) => post.slug === slug); if (currentIndex === -1) { return { prev: null, next: null }; diff --git a/lib/mdx.ts b/lib/mdx.ts index 944c964f..6e5b4d91 100644 --- a/lib/mdx.ts +++ b/lib/mdx.ts @@ -18,11 +18,61 @@ export interface ProductMdx { content: string; } +export async function getProductMetadata( + slug: string, + locale: string, +): Promise | null> { + // Map translated slug to file slug + const fileSlug = await mapSlugToFileSlug(slug, locale); + const productsDir = path.join(process.cwd(), 'data', 'products', locale); + + // Try exact slug first + let filePath = path.join(productsDir, `${fileSlug}.mdx`); + + if (!fs.existsSync(filePath)) { + // Try with -2 suffix (common in the dumped files) + filePath = path.join(productsDir, `${fileSlug}-2.mdx`); + } + + if (!fs.existsSync(filePath)) { + // Fallback to English if locale is not 'en' + if (locale !== 'en') { + const enProductsDir = path.join(process.cwd(), 'data', 'products', 'en'); + let enFilePath = path.join(enProductsDir, `${fileSlug}.mdx`); + if (!fs.existsSync(enFilePath)) { + enFilePath = path.join(enProductsDir, `${fileSlug}-2.mdx`); + } + + if (fs.existsSync(enFilePath)) { + const fileContent = fs.readFileSync(enFilePath, 'utf8'); + const { data } = matter(fileContent); + return { + slug: fileSlug, + frontmatter: { + ...data, + isFallback: true, + } as ProductFrontmatter & { isFallback?: boolean }, + }; + } + } + } else { + const fileContent = fs.readFileSync(filePath, 'utf8'); + const { data } = matter(fileContent); + + return { + slug: fileSlug, + frontmatter: data as ProductFrontmatter, + }; + } + + return null; +} + export async function getProductBySlug(slug: string, locale: string): Promise { // Map translated slug to file slug const fileSlug = await mapSlugToFileSlug(slug, locale); const productsDir = path.join(process.cwd(), 'data', 'products', locale); - + // Try exact slug first let filePath = path.join(productsDir, `${fileSlug}.mdx`); @@ -41,7 +91,7 @@ export async function getProductBySlug(slug: string, locale: string): Promise { const productsDir = path.join(process.cwd(), 'data', 'products', locale); if (!fs.existsSync(productsDir)) return []; - + const files = fs.readdirSync(productsDir); - return files.filter(file => file.endsWith('.mdx')).map(file => file.replace(/\.mdx$/, '')); + return files.filter((file) => file.endsWith('.mdx')).map((file) => file.replace(/\.mdx$/, '')); } export async function getAllProducts(locale: string): Promise { @@ -91,6 +146,19 @@ export async function getAllProducts(locale: string): Promise { allSlugs = Array.from(new Set([...slugs, ...enSlugs])); } - const products = await Promise.all(allSlugs.map(slug => getProductBySlug(slug, locale))); + const products = await Promise.all(allSlugs.map((slug) => getProductBySlug(slug, locale))); return products.filter((p): p is ProductMdx => p !== null); } + +export async function getAllProductsMetadata(locale: string): Promise[]> { + const slugs = await getAllProductSlugs(locale); + let allSlugs = slugs; + + if (locale !== 'en') { + const enSlugs = await getAllProductSlugs('en'); + allSlugs = Array.from(new Set([...slugs, ...enSlugs])); + } + + const metadata = await Promise.all(allSlugs.map((slug) => getProductMetadata(slug, locale))); + return metadata.filter((m): m is Partial => m !== null); +} diff --git a/lib/pages.ts b/lib/pages.ts index 7f6bcac1..d927bbcf 100644 --- a/lib/pages.ts +++ b/lib/pages.ts @@ -39,23 +39,42 @@ export async function getPageBySlug(slug: string, locale: string): Promise { const pagesDir = path.join(process.cwd(), 'data', 'pages', locale); if (!fs.existsSync(pagesDir)) return []; - + const files = fs.readdirSync(pagesDir); const pages = await Promise.all( files - .filter(file => file.endsWith('.mdx')) - .map(file => { + .filter((file) => file.endsWith('.mdx')) + .map((file) => { const fileSlug = file.replace(/\.mdx$/, ''); const filePath = path.join(pagesDir, file); - const fileContent = { content: fs.readFileSync(filePath, 'utf8') }; - const { data, content } = matter(fileContent.content); + const fileContent = fs.readFileSync(filePath, 'utf8'); + const { data, content } = matter(fileContent); return { slug: fileSlug, frontmatter: data as PageFrontmatter, content, }; - }) + }), ); return pages.filter((p): p is PageMdx => p !== null); } + +export async function getAllPagesMetadata(locale: string): Promise[]> { + const pagesDir = path.join(process.cwd(), 'data', 'pages', locale); + if (!fs.existsSync(pagesDir)) return []; + + const files = fs.readdirSync(pagesDir); + return files + .filter((file) => file.endsWith('.mdx')) + .map((file) => { + const fileSlug = file.replace(/\.mdx$/, ''); + const filePath = path.join(pagesDir, file); + const fileContent = fs.readFileSync(filePath, 'utf8'); + const { data } = matter(fileContent); + return { + slug: fileSlug, + frontmatter: data as PageFrontmatter, + }; + }); +}