next-sitemap.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. /* eslint-disable no-console */
  2. /* eslint-disable max-lines */
  3. /* eslint-disable jsdoc/require-returns */
  4. /* eslint-disable import/no-extraneous-dependencies */
  5. /* eslint-disable react-func/max-lines-per-function */
  6. /* eslint-disable global-require */
  7. /* eslint-disable import/no-dynamic-require */
  8. const range = require('lodash/range');
  9. const fetch = require('node-fetch');
  10. const englishChaptersData = require('./data/chapters/en.json');
  11. const { locales } = require('./i18n.json');
  12. const isProduction = process.env.NEXT_PUBLIC_VERCEL_ENV === 'production';
  13. const isDevelopment = process.env.NEXT_PUBLIC_VERCEL_ENV === 'development';
  14. const shouldGenerateAdditionalPaths =
  15. process.env.NEXT_PUBLIC_GENERATE_SITEMAP_ADDITIONAL_PATHS === 'true';
  16. const BASE_PATH =
  17. `${isDevelopment ? 'http' : 'https'}://${process.env.NEXT_PUBLIC_VERCEL_URL}` ||
  18. 'https://quran.com';
  19. const BASE_AUTH_PATH = process.env.NEXT_PUBLIC_AUTH_BASE_URL;
  20. const chapters = range(1, 115);
  21. const getAvailableCourses = async () => {
  22. const res = await fetch(`${BASE_AUTH_PATH}/courses`);
  23. const data = await res.json();
  24. return data;
  25. };
  26. const getAvailableTafsirs = async () => {
  27. const res = await fetch(`https://api.qurancdn.com/api/qdc/resources/tafsirs`);
  28. const data = await res.json();
  29. return data;
  30. };
  31. const getAvailableReciters = async () => {
  32. const res = await fetch(`https://api.qurancdn.com/api/qdc/audio/reciters`);
  33. const data = await res.json();
  34. return data;
  35. };
  36. /**
  37. * Get the alternate ref objects for a path. We append "-remove-from-here" because
  38. * next-sitemap library appends the alternate ref to the beginning
  39. * of the url of the English version rather than replacing it completely.
  40. * e.g. for english the url is /surah/al-fatihah/info
  41. * but for Arabic it becomes /ar/surah/سورة-الفاتحة/info/surah/al-fatihah/info
  42. * although we don't want "surah/al-fatihah/info" part at the end of the path.
  43. * so appending "-remove-from-here" indicator would make it easier for us to manually
  44. * process the .xml file and remove the characters starting from "-remove-from-here".
  45. * e.g. /ar/سورة-الفاتحة/1-remove-from-here/al-fatihah/1
  46. *
  47. * @param {number} chapterId
  48. * @param {boolean} appendSlug
  49. * @param {string} prefix
  50. * @param {string} suffix
  51. */
  52. const getAlternateRefs = (chapterId = null, appendSlug = true, prefix = '', suffix = '') => {
  53. return locales.map((locale) => {
  54. let href = `${BASE_PATH}/${locale}`;
  55. if (prefix) {
  56. href = `${href}/${prefix}`;
  57. }
  58. if (appendSlug) {
  59. const localeChaptersData = require(`./data/chapters/${locale}.json`);
  60. href = `${href}/${localeChaptersData[chapterId].slug}`;
  61. }
  62. if (suffix) {
  63. href = `${href}/${suffix}`;
  64. }
  65. return {
  66. href: `${href}-remove-from-here`,
  67. hreflang: locale,
  68. };
  69. });
  70. };
  71. module.exports = {
  72. siteUrl: BASE_PATH,
  73. sitemapSize: 20000,
  74. generateRobotsTxt: isProduction,
  75. exclude: [
  76. ...locales.map((locale) => `/${locale}`),
  77. '/*/product-updates*',
  78. '/*/search',
  79. '/*my-learning-plans',
  80. ],
  81. alternateRefs: locales.map((locale) => ({
  82. href: `${BASE_PATH}/${locale}`,
  83. hreflang: locale,
  84. })),
  85. transform: async (config, path) => {
  86. return {
  87. loc: path,
  88. lastmod: new Date().toISOString(),
  89. alternateRefs: config.alternateRefs ?? [],
  90. };
  91. },
  92. additionalPaths: shouldGenerateAdditionalPaths
  93. ? async (config) => {
  94. const result = [];
  95. let tafsirSlugs = [];
  96. let reciterIds = [];
  97. await getAvailableTafsirs().then((response) => {
  98. tafsirSlugs = response.tafsirs.map((tafsir) => tafsir.slug);
  99. });
  100. await getAvailableReciters().then((response) => {
  101. reciterIds = response.reciters.map((reciter) => reciter.id);
  102. });
  103. chapters.forEach((chapterId) => {
  104. // 1. add the chapter slugs in English along with the localized slugs in every locale
  105. const englishChapterSlug = englishChaptersData[chapterId].slug;
  106. result.push({
  107. loc: `/${englishChapterSlug}`,
  108. alternateRefs: getAlternateRefs(chapterId),
  109. });
  110. // 2. add slugged /surah/[chapterSlug]/info in English along with the localized slugs in every locale
  111. result.push({
  112. loc: `/surah/${englishChapterSlug}/info`,
  113. alternateRefs: getAlternateRefs(chapterId, true, 'surah', 'info'),
  114. });
  115. // 3. /reciters/[reciterId]/[chapterSlug]
  116. reciterIds.forEach((reciterId) => {
  117. const location = `/reciters/${reciterId}/${englishChapterSlug}`;
  118. result.push({
  119. loc: location,
  120. alternateRefs: getAlternateRefs(chapterId, false, '', location),
  121. });
  122. });
  123. // 4. generate the verses for each of the chapters in each locale as well
  124. range(englishChaptersData[chapterId].versesCount).forEach((verseId) => {
  125. const verseNumber = verseId + 1;
  126. const verseIdValue = verseNumber;
  127. const verseKey = `${chapterId}:${verseIdValue}`;
  128. const isAyatulKursi = chapterId === 2 && verseNumber === 255;
  129. if (isAyatulKursi) {
  130. // instead of /al-baqarah/255, we push /ayatul-kursi
  131. result.push({
  132. loc: `/ayatul-kursi`,
  133. alternateRefs: getAlternateRefs(null, false, '', 'ayatul-kursi'),
  134. });
  135. } else {
  136. result.push({
  137. loc: `/${englishChapterSlug}/${verseIdValue}`,
  138. alternateRefs: getAlternateRefs(chapterId, true, '', verseIdValue),
  139. });
  140. }
  141. // 5. add /[chapterId]/[verseId]/tafsirs route
  142. result.push({
  143. loc: `/${englishChapterSlug}/${verseIdValue}/tafsirs`,
  144. alternateRefs: getAlternateRefs(chapterId, true, '', `${verseIdValue}/tafsirs`),
  145. });
  146. // 6. /[verseKey]/tafsirs/[tafsirSlug]
  147. tafsirSlugs.forEach((tafsirSlug) => {
  148. const location = `${verseKey}/tafsirs/${tafsirSlug}`;
  149. result.push({
  150. loc: location,
  151. alternateRefs: getAlternateRefs(chapterId, false, '', location),
  152. });
  153. });
  154. // 7. /[verseKey]/reflections
  155. const reflectionsLocation = `${verseKey}/reflections`;
  156. result.push({
  157. loc: reflectionsLocation,
  158. alternateRefs: getAlternateRefs(chapterId, false, '', reflectionsLocation),
  159. });
  160. });
  161. });
  162. // 7. /juz/[juzId]
  163. range(1, 31).forEach(async (juzId) => {
  164. result.push(await config.transform(config, `/juz/${juzId}`));
  165. });
  166. // 8. /hizb/[hizbId]
  167. range(1, 61).forEach(async (hizbId) => {
  168. result.push(await config.transform(config, `/hizb/${hizbId}`));
  169. });
  170. // 9. /rub/[rubId]
  171. range(1, 241).forEach(async (rubId) => {
  172. result.push(await config.transform(config, `/rub/${rubId}`));
  173. });
  174. // 10. /page/[pageId]
  175. range(1, 605).forEach(async (pageId) => {
  176. result.push(await config.transform(config, `/page/${pageId}`));
  177. });
  178. // 11. /reciters/[reciterId]
  179. reciterIds.forEach((reciterId) => {
  180. const location = `/reciters/${reciterId}`;
  181. result.push({
  182. loc: location,
  183. alternateRefs: getAlternateRefs('', false, '', location),
  184. });
  185. });
  186. // 12. /learning-plans/[learningPlanSlug]
  187. const learningPlans = await getAvailableCourses();
  188. // TODO: handle pagination in the future when we have more than 10 learning plans
  189. learningPlans.data.forEach((learningPlan) => {
  190. const location = `/learning-plans/${learningPlan.slug}`;
  191. // TODO: handle per language learning plans e.g. Arabic learning plan should only show under /ar/[learning-plan-slug]
  192. result.push({
  193. loc: location,
  194. alternateRefs: getAlternateRefs('', false, '', location),
  195. });
  196. });
  197. return result;
  198. }
  199. : async () => {
  200. console.log('Skipping additional paths generation...');
  201. return Promise.resolve([]);
  202. },
  203. };