Skip to content

Commit 4cf8dd9

Browse files
authored
feat(seo): modularize metadata pipeline and resolve Bing SEO scan issues (#839)
* feat(seo): modularize metadata pipeline and resolve Bing SEO scan issues * feat(seo): add regional hreflang coverage and fix Bing metadata quality signals * fix(seo): resolve Bing metadata findings and harden multilingual SEO signals
1 parent 9da1a06 commit 4cf8dd9

31 files changed

Lines changed: 1870 additions & 1164 deletions

SortVision/public/sw.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
// SortVision Service Worker
22
// Provides offline capabilities and caching for better performance
33

4-
const CACHE_NAME = 'sortvision-v1.0.2';
5-
const STATIC_CACHE = 'sortvision-static-v1.0.2';
6-
const DYNAMIC_CACHE = 'sortvision-dynamic-v1.0.2';
4+
// Version may be injected at build time to align app + service worker.
5+
// Fallback keeps current behavior when no injection is configured.
6+
const APP_VERSION =
7+
(typeof self !== 'undefined' && self.__APP_VERSION__) || '2.0.0';
8+
const CACHE_NAME = `sortvision-v${APP_VERSION}`;
9+
const STATIC_CACHE = `sortvision-static-v${APP_VERSION}`;
10+
const DYNAMIC_CACHE = `sortvision-dynamic-v${APP_VERSION}`;
711

812
// Check if we're in development mode
913
const isDev =
@@ -238,8 +242,9 @@ self.addEventListener('message', event => {
238242
}
239243

240244
if (event.data && event.data.type === 'GET_VERSION') {
241-
event.ports[0].postMessage({ version: CACHE_NAME });
245+
event.ports[0].postMessage({ version: APP_VERSION });
242246
}
243247
});
244248

245249
debugLog('SortVision service worker loaded successfully.');
250+
debugLog('SortVision app version:', APP_VERSION);

SortVision/src/app/[[...slug]]/page.jsx

Lines changed: 149 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,87 @@ import {
1111
getLearningOutcomes,
1212
} from '../../utils/seo';
1313

14+
const OG_LOCALE_MAP = {
15+
en: 'en_US',
16+
es: 'es_ES',
17+
hi: 'hi_IN',
18+
fr: 'fr_FR',
19+
de: 'de_DE',
20+
zh: 'zh_CN',
21+
bn: 'bn_BD',
22+
ja: 'ja_JP',
23+
};
24+
25+
const HREFLANG_REGION_ALIASES = {
26+
en: ['en-US', 'en-GB', 'en-IN'],
27+
es: ['es-ES', 'es-MX', 'es-AR'],
28+
hi: ['hi-IN'],
29+
fr: ['fr-FR', 'fr-CA'],
30+
de: ['de-DE', 'de-AT'],
31+
zh: ['zh-CN', 'zh-TW', 'zh-HK'],
32+
bn: ['bn-BD', 'bn-IN'],
33+
ja: ['ja-JP'],
34+
};
35+
36+
const ensureMinimumDescriptionLength = (
37+
description,
38+
language,
39+
contextLabel = 'page'
40+
) => {
41+
if (!description) return description;
42+
const MIN_LENGTH = 150;
43+
const MAX_LENGTH = 165;
44+
if (description.length >= MIN_LENGTH) {
45+
return description.length > MAX_LENGTH
46+
? `${description.slice(0, MAX_LENGTH - 1).trimEnd()}…`
47+
: description;
48+
}
49+
50+
const EXPANSION_BY_LANGUAGE = {
51+
en:
52+
contextLabel === 'homepage'
53+
? ' Explore sorting animations, compare runtime behavior, and strengthen DSA interview preparation.'
54+
: ` Explore this ${contextLabel} with interactive visuals and interview-focused DSA guidance.`,
55+
es:
56+
contextLabel === 'homepage'
57+
? ' Explora animaciones de ordenamiento, compara rendimiento y fortalece tu preparación para entrevistas DSA.'
58+
: ' Explora esta página con visualización interactiva y guía práctica para entrevistas DSA.',
59+
hi:
60+
contextLabel === 'homepage'
61+
? ' सॉर्टिंग एनीमेशन देखें, प्रदर्शन की तुलना करें और DSA इंटरव्यू तैयारी मजबूत करें।'
62+
: ' इस पेज को इंटरएक्टिव विजुअल्स और इंटरव्यू-केंद्रित DSA मार्गदर्शन के साथ समझें।',
63+
fr:
64+
contextLabel === 'homepage'
65+
? ' Explorez les animations de tri, comparez les performances et renforcez votre préparation DSA.'
66+
: ' Explorez cette page avec des visuels interactifs et des conseils DSA orientés entretien.',
67+
de:
68+
contextLabel === 'homepage'
69+
? ' Entdecke Sortieranimationen, vergleiche Laufzeiten und verbessere deine DSA-Interviewvorbereitung.'
70+
: ' Entdecke diese Seite mit interaktiven Visualisierungen und DSA-Interviewhilfe.',
71+
zh:
72+
contextLabel === 'homepage'
73+
? ' 通过交互式排序动画对比性能表现,系统提升你的 DSA 面试准备效率。'
74+
: ' 通过交互式可视化学习本页面内容,并获得面试导向的 DSA 学习指引。',
75+
bn:
76+
contextLabel === 'homepage'
77+
? ' ইন্টারঅ্যাকটিভ সোর্টিং অ্যানিমেশন দেখে পারফরম্যান্স তুলনা করুন এবং DSA ইন্টারভিউ প্রস্তুতি বাড়ান।'
78+
: ' ইন্টারঅ্যাকটিভ ভিজুয়াল ও ইন্টারভিউ-কেন্দ্রিক DSA গাইডসহ এই পেজটি অন্বেষণ করুন।',
79+
ja:
80+
contextLabel === 'homepage'
81+
? ' ソートのアニメーションで挙動と性能を比較し、DSA面接対策を効果的に進められます。'
82+
: ' このページをインタラクティブ表示で学び、面接向けDSA理解を深められます。',
83+
};
84+
85+
const expansion = EXPANSION_BY_LANGUAGE[language] || EXPANSION_BY_LANGUAGE.en;
86+
const needed = MIN_LENGTH - description.length;
87+
const safeAppend = expansion.slice(0, Math.max(needed + 8, 0));
88+
const merged = `${description}${safeAppend}`.trim();
89+
90+
return merged.length > MAX_LENGTH
91+
? `${merged.slice(0, MAX_LENGTH - 1).trimEnd()}…`
92+
: merged;
93+
};
94+
1495
// Generate metadata dynamically based on the route
1596
export async function generateMetadata({ params, searchParams }) {
1697
// Await params as they are now a Promise in Next.js 16
@@ -74,6 +155,12 @@ export async function generateMetadata({ params, searchParams }) {
74155
add(lang, lang);
75156
});
76157

158+
// Add regional aliases that map to the same language URL.
159+
supportedLanguages.forEach(lang => {
160+
const aliases = HREFLANG_REGION_ALIASES[lang] || [];
161+
aliases.forEach(alias => add(alias, lang));
162+
});
163+
77164
// Add x-default pointing to English (canonical)
78165
alternates['x-default'] = `https://www.sortvision.com${basePath}`;
79166

@@ -85,6 +172,23 @@ export async function generateMetadata({ params, searchParams }) {
85172
const algorithm = slug[2];
86173
const tab = slug[1] || 'config';
87174
const metaTags = getAlgorithmMetaTags(algorithm, language);
175+
const tabTitleMap = {
176+
config: 'Configuration',
177+
details: 'Step-by-step Details',
178+
metrics: 'Performance Metrics',
179+
};
180+
const tabTitleSuffix = tabTitleMap[tab] || 'Algorithm View';
181+
const tabDescriptionMap = {
182+
config:
183+
'Configure inputs, speed, and array size to explore algorithm behavior interactively.',
184+
details:
185+
'Follow each comparison and swap with step-by-step algorithm execution insights.',
186+
metrics:
187+
'Analyze time complexity, operation counts, and runtime performance characteristics.',
188+
};
189+
const tabDescriptionSuffix =
190+
tabDescriptionMap[tab] ||
191+
'Explore this algorithm view with interactive educational controls.';
88192

89193
const basePath = `/algorithms/${tab}/${algorithm}`;
90194
const currentUrl = language === 'en' ? basePath : `/${language}${basePath}`;
@@ -99,12 +203,16 @@ export async function generateMetadata({ params, searchParams }) {
99203
: `/${language}${canonicalBasePath}`;
100204

101205
return {
102-
title: metaTags.title,
103-
description: metaTags.description,
206+
title: `${metaTags.title} | ${tabTitleSuffix}`,
207+
description: ensureMinimumDescriptionLength(
208+
`${metaTags.description} ${tabDescriptionSuffix}`,
209+
language,
210+
`${algorithm} algorithm`
211+
),
104212
keywords: metaTags.keywords,
105213
authors: [{ name: 'Prabal Patra' }],
106214
robots:
107-
'index, follow, noarchive, max-image-preview:large, max-snippet:-1, max-video-preview:-1',
215+
'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1',
108216
openGraph: {
109217
type: 'website',
110218
url: `https://www.sortvision.com${currentUrl}`,
@@ -119,18 +227,7 @@ export async function generateMetadata({ params, searchParams }) {
119227
},
120228
],
121229
siteName: 'SortVision',
122-
locale:
123-
language === 'es'
124-
? 'es_ES'
125-
: language === 'hi'
126-
? 'hi_IN'
127-
: language === 'fr'
128-
? 'fr_FR'
129-
: language === 'de'
130-
? 'de_DE'
131-
: language === 'zh'
132-
? 'zh_CN'
133-
: 'en_US',
230+
locale: OG_LOCALE_MAP[language] || 'en_US',
134231
},
135232
twitter: {
136233
card: 'summary_large_image',
@@ -231,19 +328,45 @@ export async function generateMetadata({ params, searchParams }) {
231328
} else {
232329
metaTags = getContributionsMetaTags(language);
233330
}
331+
const sectionTitleMap = {
332+
overview: 'Overview',
333+
guide: 'Guide',
334+
ssoc: 'Leaderboard',
335+
};
336+
const sectionTitleSuffix = sectionTitleMap[section] || 'Contributions';
337+
const languageTitleSuffix =
338+
language === 'en' ? '' : ` (${language.toUpperCase()})`;
339+
const sectionDescriptionMap = {
340+
overview:
341+
'Browse contributor profiles, pull requests, issues, and overall community impact.',
342+
guide:
343+
'Learn contribution workflow, setup steps, standards, and PR process for SortVision.',
344+
ssoc: 'Track SSOC leaderboard standings, points, and contributor activity across the program.',
345+
};
346+
const sectionDescriptionSuffix =
347+
sectionDescriptionMap[section] ||
348+
'Explore this contribution section for project and community insights.';
349+
const languageDescriptionSuffix =
350+
language === 'en'
351+
? ''
352+
: ` Localized for ${language.toUpperCase()} users.`;
234353

235354
const basePath = contributorId
236355
? `/contributions/${section}/${contributorId}`
237356
: `/contributions/${section}`;
238357
const currentUrl = language === 'en' ? basePath : `/${language}${basePath}`;
239358

240359
return {
241-
title: metaTags.title,
242-
description: metaTags.description,
360+
title: `${metaTags.title} | ${sectionTitleSuffix}${languageTitleSuffix}`,
361+
description: ensureMinimumDescriptionLength(
362+
`${metaTags.description} ${sectionDescriptionSuffix}${languageDescriptionSuffix}`,
363+
language,
364+
`${section} contributions`
365+
),
243366
keywords: metaTags.keywords,
244367
authors: [{ name: 'Prabal Patra' }],
245368
robots:
246-
'index, follow, noarchive, max-image-preview:large, max-snippet:-1, max-video-preview:-1',
369+
'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1',
247370
openGraph: {
248371
type: 'website',
249372
url: `https://www.sortvision.com${currentUrl}`,
@@ -258,18 +381,7 @@ export async function generateMetadata({ params, searchParams }) {
258381
},
259382
],
260383
siteName: 'SortVision',
261-
locale:
262-
language === 'es'
263-
? 'es_ES'
264-
: language === 'hi'
265-
? 'hi_IN'
266-
: language === 'fr'
267-
? 'fr_FR'
268-
: language === 'de'
269-
? 'de_DE'
270-
: language === 'zh'
271-
? 'zh_CN'
272-
: 'en_US',
384+
locale: OG_LOCALE_MAP[language] || 'en_US',
273385
},
274386
twitter: {
275387
card: 'summary_large_image',
@@ -314,11 +426,15 @@ export async function generateMetadata({ params, searchParams }) {
314426

315427
return {
316428
title: metaTags.title,
317-
description: metaTags.description,
429+
description: ensureMinimumDescriptionLength(
430+
metaTags.description,
431+
language,
432+
'homepage'
433+
),
318434
keywords: metaTags.keywords,
319435
authors: [{ name: 'Prabal Patra' }],
320436
robots:
321-
'index, follow, noarchive, max-image-preview:large, max-snippet:-1, max-video-preview:-1',
437+
'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1',
322438
openGraph: {
323439
type: 'website',
324440
url: `https://www.sortvision.com${currentUrl}`,
@@ -333,18 +449,7 @@ export async function generateMetadata({ params, searchParams }) {
333449
},
334450
],
335451
siteName: 'SortVision',
336-
locale:
337-
language === 'es'
338-
? 'es_ES'
339-
: language === 'hi'
340-
? 'hi_IN'
341-
: language === 'fr'
342-
? 'fr_FR'
343-
: language === 'de'
344-
? 'de_DE'
345-
: language === 'zh'
346-
? 'zh_CN'
347-
: 'en_US',
452+
locale: OG_LOCALE_MAP[language] || 'en_US',
348453
},
349454
twitter: {
350455
card: 'summary_large_image',

0 commit comments

Comments
 (0)