fix(ci): restore full localized sitemap coverage and strict 404 validation
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 18s
Build & Deploy / 🧪 QA (push) Successful in 2m7s
Build & Deploy / 🏗️ Build (push) Successful in 2m49s
Build & Deploy / 🚀 Deploy (push) Failing after 22s
Build & Deploy / 🧪 Smoke Test (push) Has been skipped
Build & Deploy / ⚡ Lighthouse (push) Has been skipped
Build & Deploy / ♿ WCAG (push) Has been skipped
Build & Deploy / 🛡️ Quality Gates (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 5s
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 18s
Build & Deploy / 🧪 QA (push) Successful in 2m7s
Build & Deploy / 🏗️ Build (push) Successful in 2m49s
Build & Deploy / 🚀 Deploy (push) Failing after 22s
Build & Deploy / 🧪 Smoke Test (push) Has been skipped
Build & Deploy / ⚡ Lighthouse (push) Has been skipped
Build & Deploy / ♿ WCAG (push) Has been skipped
Build & Deploy / 🛡️ Quality Gates (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 5s
This commit is contained in:
@@ -3,6 +3,7 @@ import { MetadataRoute } from 'next';
|
|||||||
import { getAllProductsMetadata } from '@/lib/mdx';
|
import { getAllProductsMetadata } from '@/lib/mdx';
|
||||||
import { getAllPostsMetadata } from '@/lib/blog';
|
import { getAllPostsMetadata } from '@/lib/blog';
|
||||||
import { getAllPagesMetadata } from '@/lib/pages';
|
import { getAllPagesMetadata } from '@/lib/pages';
|
||||||
|
import { mapFileSlugToTranslated } from '@/lib/slugs';
|
||||||
|
|
||||||
export const revalidate = 3600; // Revalidate every hour
|
export const revalidate = 3600; // Revalidate every hour
|
||||||
|
|
||||||
@@ -12,28 +13,45 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|||||||
: config.baseUrl || 'https://klz-cables.com';
|
: config.baseUrl || 'https://klz-cables.com';
|
||||||
const locales = ['de', 'en'];
|
const locales = ['de', 'en'];
|
||||||
|
|
||||||
const routes = [
|
|
||||||
'',
|
|
||||||
'/blog',
|
|
||||||
'/contact',
|
|
||||||
'/team',
|
|
||||||
'/products',
|
|
||||||
'/products/low-voltage-cables',
|
|
||||||
'/products/medium-voltage-cables',
|
|
||||||
'/products/high-voltage-cables',
|
|
||||||
'/products/solar-cables',
|
|
||||||
];
|
|
||||||
|
|
||||||
const sitemapEntries: MetadataRoute.Sitemap = [];
|
const sitemapEntries: MetadataRoute.Sitemap = [];
|
||||||
|
|
||||||
for (const locale of locales) {
|
for (const locale of locales) {
|
||||||
|
// Helper to generate localized URL Segment
|
||||||
|
const getLocalizedRoute = async (pageKey: string) => {
|
||||||
|
if (pageKey === '') return '';
|
||||||
|
const translated = await mapFileSlugToTranslated(pageKey, locale);
|
||||||
|
return `/${translated}`;
|
||||||
|
};
|
||||||
|
|
||||||
// Static routes
|
// Static routes
|
||||||
for (const route of routes) {
|
const staticPages = ['', 'blog', 'contact', 'team', 'products'];
|
||||||
|
for (const page of staticPages) {
|
||||||
|
const localizedRoute = await getLocalizedRoute(page);
|
||||||
sitemapEntries.push({
|
sitemapEntries.push({
|
||||||
url: `${baseUrl}/${locale}${route}`,
|
url: `${baseUrl}/${locale}${localizedRoute}`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: route === '' ? 'daily' : 'weekly',
|
changeFrequency: page === '' ? 'daily' : 'weekly',
|
||||||
priority: route === '' ? 1 : 0.8,
|
priority: page === '' ? 1 : 0.8,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Categories routes
|
||||||
|
const productCategories = [
|
||||||
|
'low-voltage-cables',
|
||||||
|
'medium-voltage-cables',
|
||||||
|
'high-voltage-cables',
|
||||||
|
'solar-cables',
|
||||||
|
];
|
||||||
|
|
||||||
|
const translatedProducts = await mapFileSlugToTranslated('products', locale);
|
||||||
|
|
||||||
|
for (const category of productCategories) {
|
||||||
|
const translatedCategory = await mapFileSlugToTranslated(category, locale);
|
||||||
|
sitemapEntries.push({
|
||||||
|
url: `${baseUrl}/${locale}/${translatedProducts}/${translatedCategory}`,
|
||||||
|
lastModified: new Date(),
|
||||||
|
changeFrequency: 'weekly',
|
||||||
|
priority: 0.8,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,8 +62,11 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|||||||
|
|
||||||
const category =
|
const category =
|
||||||
product.frontmatter.categories[0]?.toLowerCase().replace(/\s+/g, '-') || 'other';
|
product.frontmatter.categories[0]?.toLowerCase().replace(/\s+/g, '-') || 'other';
|
||||||
|
const translatedCategory = await mapFileSlugToTranslated(category, locale);
|
||||||
|
const translatedSlug = await mapFileSlugToTranslated(product.slug, locale);
|
||||||
|
|
||||||
sitemapEntries.push({
|
sitemapEntries.push({
|
||||||
url: `${baseUrl}/${locale}/products/${category}/${product.slug}`,
|
url: `${baseUrl}/${locale}/${translatedProducts}/${translatedCategory}/${translatedSlug}`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'monthly',
|
changeFrequency: 'monthly',
|
||||||
priority: 0.7,
|
priority: 0.7,
|
||||||
@@ -53,12 +74,15 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Blog posts
|
// Blog posts
|
||||||
|
const translatedBlog = await mapFileSlugToTranslated('blog', locale);
|
||||||
const postsMetadata = await getAllPostsMetadata(locale);
|
const postsMetadata = await getAllPostsMetadata(locale);
|
||||||
for (const post of postsMetadata) {
|
for (const post of postsMetadata) {
|
||||||
if (!post.frontmatter || !post.slug) continue;
|
if (!post.frontmatter || !post.slug) continue;
|
||||||
|
|
||||||
|
const translatedSlug = await mapFileSlugToTranslated(post.slug, locale);
|
||||||
|
|
||||||
sitemapEntries.push({
|
sitemapEntries.push({
|
||||||
url: `${baseUrl}/${locale}/blog/${post.slug}`,
|
url: `${baseUrl}/${locale}/${translatedBlog}/${translatedSlug}`,
|
||||||
lastModified: new Date(post.frontmatter.date),
|
lastModified: new Date(post.frontmatter.date),
|
||||||
changeFrequency: 'monthly',
|
changeFrequency: 'monthly',
|
||||||
priority: 0.6,
|
priority: 0.6,
|
||||||
@@ -70,8 +94,10 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|||||||
for (const page of pagesMetadata) {
|
for (const page of pagesMetadata) {
|
||||||
if (!page.slug) continue;
|
if (!page.slug) continue;
|
||||||
|
|
||||||
|
const translatedSlug = await mapFileSlugToTranslated(page.slug, locale);
|
||||||
|
|
||||||
sitemapEntries.push({
|
sitemapEntries.push({
|
||||||
url: `${baseUrl}/${locale}/${page.slug}`,
|
url: `${baseUrl}/${locale}/${translatedSlug}`,
|
||||||
lastModified: new Date(),
|
lastModified: new Date(),
|
||||||
changeFrequency: 'monthly',
|
changeFrequency: 'monthly',
|
||||||
priority: 0.5,
|
priority: 0.5,
|
||||||
|
|||||||
67
lib/slugs.ts
67
lib/slugs.ts
@@ -1,27 +1,34 @@
|
|||||||
import { getTranslations } from 'next-intl/server';
|
import enMessages from '../messages/en.json';
|
||||||
|
import deMessages from '../messages/de.json';
|
||||||
|
|
||||||
|
type Messages = typeof enMessages;
|
||||||
|
const getMessages = (locale: string): Messages => {
|
||||||
|
return locale === 'de' ? (deMessages as any) : enMessages;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps a translated slug to original file slug
|
* Maps a translated slug to original file slug
|
||||||
* @param translatedSlug - The slug from URL (translated)
|
* @param translatedSlug - The slug from URL (translated)
|
||||||
* @param _locale - The current locale (unused, kept for API consistency)
|
* @param locale - The current locale
|
||||||
* @returns The original file slug, or input slug if no mapping exists
|
* @returns The original file slug, or input slug if no mapping exists
|
||||||
*/
|
*/
|
||||||
export async function mapSlugToFileSlug(translatedSlug: string, _locale: string): Promise<string> {
|
export async function mapSlugToFileSlug(translatedSlug: string, locale: string): Promise<string> {
|
||||||
const t = await getTranslations('Slugs');
|
const messages = getMessages(locale);
|
||||||
|
const slugs = messages.Slugs;
|
||||||
|
|
||||||
// Check pages
|
// Check pages
|
||||||
if (t.has(`pages.${translatedSlug}`)) {
|
if (slugs.pages && translatedSlug in slugs.pages) {
|
||||||
return t(`pages.${translatedSlug}`);
|
return slugs.pages[translatedSlug as keyof typeof slugs.pages];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check products
|
// Check products
|
||||||
if (t.has(`products.${translatedSlug}`)) {
|
if (slugs.products && translatedSlug in slugs.products) {
|
||||||
return t(`products.${translatedSlug}`);
|
return slugs.products[translatedSlug as keyof typeof slugs.products];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check categories
|
// Check categories
|
||||||
if (t.has(`categories.${translatedSlug}`)) {
|
if (slugs.categories && translatedSlug in slugs.categories) {
|
||||||
return t(`categories.${translatedSlug}`);
|
return slugs.categories[translatedSlug as keyof typeof slugs.categories];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return original slug if no mapping found
|
// Return original slug if no mapping found
|
||||||
@@ -31,23 +38,20 @@ export async function mapSlugToFileSlug(translatedSlug: string, _locale: string)
|
|||||||
/**
|
/**
|
||||||
* Maps an original file slug to translated slug for a locale
|
* Maps an original file slug to translated slug for a locale
|
||||||
* @param fileSlug - The original file slug
|
* @param fileSlug - The original file slug
|
||||||
* @param _locale - The target locale (unused, kept for API consistency)
|
* @param locale - The target locale
|
||||||
* @returns The translated slug, or input slug if no mapping exists
|
* @returns The translated slug, or input slug if no mapping exists
|
||||||
*/
|
*/
|
||||||
export async function mapFileSlugToTranslated(fileSlug: string, _locale: string): Promise<string> {
|
export async function mapFileSlugToTranslated(fileSlug: string, locale: string): Promise<string> {
|
||||||
const t = await getTranslations('Slugs');
|
const messages = getMessages(locale);
|
||||||
|
const slugs = messages.Slugs;
|
||||||
|
|
||||||
// Find the key that maps to this file slug
|
const sections = [slugs.pages, slugs.products, slugs.categories];
|
||||||
const sections = ['pages', 'products', 'categories'];
|
|
||||||
|
|
||||||
for (const section of sections) {
|
for (const sectionData of sections) {
|
||||||
if (t.has(section)) {
|
if (sectionData && typeof sectionData === 'object') {
|
||||||
const sectionData = t.raw(section);
|
for (const [translatedSlug, mappedSlug] of Object.entries(sectionData)) {
|
||||||
if (sectionData && typeof sectionData === 'object') {
|
if (mappedSlug === fileSlug) {
|
||||||
for (const [translatedSlug, mappedSlug] of Object.entries(sectionData)) {
|
return translatedSlug;
|
||||||
if (mappedSlug === fileSlug) {
|
|
||||||
return translatedSlug;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,17 +64,18 @@ export async function mapFileSlugToTranslated(fileSlug: string, _locale: string)
|
|||||||
/**
|
/**
|
||||||
* Gets all translated slugs for a section
|
* Gets all translated slugs for a section
|
||||||
* @param section - The section name (pages, products, categories)
|
* @param section - The section name (pages, products, categories)
|
||||||
* @param _locale - The current locale (unused, kept for API consistency)
|
* @param locale - The current locale
|
||||||
* @returns Object mapping translated slugs to file slugs
|
* @returns Object mapping translated slugs to file slugs
|
||||||
*/
|
*/
|
||||||
export async function getSlugMappings(section: 'pages' | 'products' | 'categories', _locale: string): Promise<Record<string, string>> {
|
export async function getSlugMappings(
|
||||||
const t = await getTranslations('Slugs');
|
section: 'pages' | 'products' | 'categories',
|
||||||
|
locale: string,
|
||||||
|
): Promise<Record<string, string>> {
|
||||||
|
const messages = getMessages(locale);
|
||||||
|
const sectionData = messages.Slugs[section];
|
||||||
|
|
||||||
if (t.has(section)) {
|
if (sectionData && typeof sectionData === 'object') {
|
||||||
const sectionData = t.raw(section);
|
return sectionData as Record<string, string>;
|
||||||
if (sectionData && typeof (sectionData as any) === 'object') {
|
|
||||||
return sectionData as Record<string, string>;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -48,12 +48,13 @@ async function main() {
|
|||||||
try {
|
try {
|
||||||
const res = await axios.get(u, {
|
const res = await axios.get(u, {
|
||||||
headers: { Cookie: `klz_gatekeeper_session=${gatekeeperPassword}` },
|
headers: { Cookie: `klz_gatekeeper_session=${gatekeeperPassword}` },
|
||||||
|
validateStatus: (status) => status < 400,
|
||||||
});
|
});
|
||||||
const filename = `page-${i}.html`;
|
const filename = `page-${i}.html`;
|
||||||
fs.writeFileSync(path.join(outputDir, filename), res.data);
|
fs.writeFileSync(path.join(outputDir, filename), res.data);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error(`❌ HTTP Error fetching ${u}: ${err.message}`);
|
console.error(`❌ HTTP Error fetching ${u}: ${err.message}`);
|
||||||
throw err;
|
throw new Error(`Failed to fetch page: ${u} - ${err.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user