From 6f9ac13d0c31f38a809a00223c760d36c4a9c51e Mon Sep 17 00:00:00 2001 From: sina_sajjadi Date: Wed, 15 Jan 2025 17:20:56 +0330 Subject: [PATCH] feat: add robots.txt and sitemap.xml for improved SEO, implement custom 404 page with localization support --- next-sitemap.config.js | 43 ++++-- public/assets/images/Icon_Illustration.webp | Bin 0 -> 2746 bytes public/locales/en/common.json | 6 +- public/robots.txt | 10 ++ public/sitemap-0.xml | 27 ++++ public/sitemap.xml | 5 + src/components/common/default-seo.tsx | 46 +++--- src/components/context/audio-conext.tsx | 2 +- src/components/layout/header.tsx | 2 +- src/components/layout/sidebar.tsx | 5 +- src/components/modals/donate-modal.tsx | 148 ++++++++++++-------- src/components/modals/search-modal.tsx | 4 +- src/components/sidebar/categories.tsx | 5 +- src/components/sidebar/famous.tsx | 4 +- src/components/sidebar/nearby.tsx | 4 +- src/components/sidebar/search.tsx | 4 +- src/components/sidebar/tabs.tsx | 4 +- src/components/sidebar/today.tsx | 4 +- src/pages/404.tsx | 34 +++++ src/pages/duas/[slug].tsx | 17 ++- 20 files changed, 260 insertions(+), 114 deletions(-) create mode 100644 public/assets/images/Icon_Illustration.webp create mode 100644 public/robots.txt create mode 100644 public/sitemap-0.xml create mode 100644 public/sitemap.xml create mode 100644 src/pages/404.tsx diff --git a/next-sitemap.config.js b/next-sitemap.config.js index 5f2b3ce..fd23d65 100644 --- a/next-sitemap.config.js +++ b/next-sitemap.config.js @@ -1,12 +1,31 @@ -const config = { - siteUrl: 'https://www.example.com', - generateRobotsTxt: true, - robotsTxtOptions: { - policies: [ - { userAgent: '*', allow: '/', disallow: ['/api/', '/admin/', '/private/'] }, - ], - }, - }; - - module.exports = config; - \ No newline at end of file +// next-sitemap.config.js +module.exports = { + siteUrl: 'https://duasapp.com', // Replace with your site's URL + generateRobotsTxt: true, // Generates robots.txt alongside the sitemap + changefreq: 'monthly', // Set the frequency of page changes + priority: 0.8, // Default priority for pages + sitemapSize: 5000, // Maximum entries per sitemap file + exclude: ['/admin/*', '/dashboard/*', '/404*'], // Exclude specific paths + additionalPaths: async (config) => { + // Custom static paths + const staticPaths = [ + await config.transform(config, '/about'), + ]; + + // Dynamic Dua paths (replace this with API call or database query in production) + const duaSlugs = ["etiquette-of-reciting-the-request-for-entry", "etiquettes-of-carrying-the-janazah", "dua-abu-hamza-thumali"]; // Example dynamic IDs + const dynamicPaths = duaSlugs.map((slug) => ({ + loc: `/duas/${slug}`, + lastmod: new Date().toISOString(), + changefreq: 'monthly', + priority: 0.9, + })); + + return [...staticPaths, ...dynamicPaths]; + }, + robotsTxtOptions: { + additionalSitemaps: [ + 'https://duasapp.com/sitemap-0.xml', // Add other sitemaps if necessary + ], + }, +}; diff --git a/public/assets/images/Icon_Illustration.webp b/public/assets/images/Icon_Illustration.webp new file mode 100644 index 0000000000000000000000000000000000000000..8dfc330987278af61b974a1281d152f5eeccc724 GIT binary patch literal 2746 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 Ad;kCd literal 0 HcmV?d00001 diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 0609db6..926179f 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -40,5 +40,9 @@ "monthly": "Monthly", "enter_desired_amount": "Enter the desired amount...", "donate_now": "Donate Now", - "credits": "Credits" + "credits": "Credits", + "notFoundImageAlt": "Not Found Illustration", + "ohNo": "Oh No!", + "somethingWentWrong": "Something went wrong!", + "backToHomePage": "Back to Home Page" } diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..960f5b3 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,10 @@ +# * +User-agent: * +Allow: / + +# Host +Host: https://duasapp.com + +# Sitemaps +Sitemap: https://duasapp.com/sitemap.xml +Sitemap: https://duasapp.com/sitemap-0.xml diff --git a/public/sitemap-0.xml b/public/sitemap-0.xml new file mode 100644 index 0000000..e90969b --- /dev/null +++ b/public/sitemap-0.xml @@ -0,0 +1,27 @@ + + +https://duasapp.com2025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/about2025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/es/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/de/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/uz/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/pt/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/bn/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/zh/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/az/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/ur/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/fr/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/tr/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/id/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/sw/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/ru/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/ar/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/tg/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/fa/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/gu/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/ks/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/ha/4042025-01-15T13:11:26.470Zmonthly0.8 +https://duasapp.com/dua/etiquette-of-reciting-the-request-for-entry2025-01-15T13:11:26.470Zdaily0.9 +https://duasapp.com/dua/etiquettes-of-carrying-the-janazah2025-01-15T13:11:26.470Zdaily0.9 +https://duasapp.com/dua/dua-abu-hamza-thumali2025-01-15T13:11:26.470Zdaily0.9 + \ No newline at end of file diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..a763e05 --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,5 @@ + + +https://duasapp.com/sitemap-0.xml +https://duasapp.com/sitemap-0.xml + \ No newline at end of file diff --git a/src/components/common/default-seo.tsx b/src/components/common/default-seo.tsx index 721c561..83a80cb 100644 --- a/src/components/common/default-seo.tsx +++ b/src/components/common/default-seo.tsx @@ -1,3 +1,5 @@ +// components/DefaultSeo.js +import React from "react"; import { DefaultSeo as NextDefaultSeo } from "next-seo"; interface DefaultSeoProps { @@ -12,7 +14,7 @@ interface DefaultSeoProps { const DefaultSeo: React.FC = ({ type = "site", - title = "Dua Site", + title = "Dua's Site", description = "A comprehensive collection of Duas", keywords = "dua, islam, prayer, supplication", url = "https://duasapp.com", @@ -25,38 +27,38 @@ const DefaultSeo: React.FC = ({ ? { "@context": "https://schema.org/", "@type": "WebSite", - "name": title, - "url": url, - "description": description, - "publisher": { + name: title, + url: url, + description: description, + publisher: { "@type": "Organization", - "name": "DuasApp", - "url": url, - "logo": { + name: "DuasApp", + url: url, + logo: { "@type": "ImageObject", - "url": "/assets/images/Hosseiniye.svg", + url: `${url}/assets/images/Hosseiniye.svg`, }, }, - "potentialAction": { + potentialAction: { "@type": "SearchAction", - "target": `${url}/?search={search_term_string}`, + target: `${url}/?search={search_term_string}`, "query-input": "required name=search_term_string", }, - "inLanguage": "en", + inLanguage: "en", } : { "@context": "https://schema.org/", "@type": "CreativeWork", - "name": title, - "url": url, - "description": description, - "text": text, - "translation": { + name: title, + url: url, + description: description, + text: text, + translation: { "@type": "CreativeWork", - "name": "Translation", - "text": translation, + name: "Translation", + text: translation, }, - "keywords": keywords, + keywords: keywords, }; return ( @@ -72,7 +74,7 @@ const DefaultSeo: React.FC = ({ description: description, images: [ { - url: "/assets/images/Hosseiniye.svg", + url: `${url}/assets/images/Hosseiniye.svg`, width: 1200, height: 630, alt: type === "site" ? "Dua Site" : title, @@ -92,7 +94,7 @@ const DefaultSeo: React.FC = ({ charset: "utf-8", }, { - name: "apple-mobile-web-app-capable", + name: "mobile-web-app-capable", content: "yes", }, { diff --git a/src/components/context/audio-conext.tsx b/src/components/context/audio-conext.tsx index 9c1523e..9363803 100644 --- a/src/components/context/audio-conext.tsx +++ b/src/components/context/audio-conext.tsx @@ -79,7 +79,7 @@ export const AudioProvider: React.FC<{ children: ReactNode }> = ({ setAudio(selectedAudio || audioResponse.data.results[0] || null); } catch (error) { - console.error("Error fetching audios:", error); + console.log("Error fetching audios:", error); } }; diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx index 243817e..6364f02 100644 --- a/src/components/layout/header.tsx +++ b/src/components/layout/header.tsx @@ -16,7 +16,7 @@ const Header = () => { }`} aria-label="Main Navigation" > -
+
{/* Logo Section */}
diff --git a/src/components/layout/sidebar.tsx b/src/components/layout/sidebar.tsx index fd64757..d2d82ec 100644 --- a/src/components/layout/sidebar.tsx +++ b/src/components/layout/sidebar.tsx @@ -6,14 +6,15 @@ function SideBar() { const router = useRouter(); const params = useParams(); const slug = params?.slug as string; + console.log(router.pathname , router.pathname.includes("/404")); - if (router.pathname.includes("/about") || router.pathname.includes("/last-read")) { + if (router.pathname.includes("/about")) { return null; } return (
diff --git a/src/components/modals/search-modal.tsx b/src/components/modals/search-modal.tsx index 2433467..fb0b25e 100644 --- a/src/components/modals/search-modal.tsx +++ b/src/components/modals/search-modal.tsx @@ -25,8 +25,8 @@ const SearchModal: React.FC = () => { const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]"); const updatedDuas = [...lastReadDuas, dua]; localStorage.setItem("last-read", JSON.stringify(updatedDuas)); - const slug = dua.title.toLowerCase().replaceAll(" ", "-"); - router.push(`/duas/${slug}-${dua.id}`); + const slug = dua.slug + router.push(`/duas/${slug}`); closeModal(); }; diff --git a/src/components/sidebar/categories.tsx b/src/components/sidebar/categories.tsx index 27013d9..2e8f64f 100644 --- a/src/components/sidebar/categories.tsx +++ b/src/components/sidebar/categories.tsx @@ -34,6 +34,7 @@ const Categories: React.FC = ({ { name: category.name, type: "children", data: category.children }, ]); }; +console.log(data); const openDua = (dua: Dua) => { const lastReadDuas = JSON.parse( @@ -41,8 +42,8 @@ const Categories: React.FC = ({ ); const updatedDuas = [...lastReadDuas, dua]; localStorage.setItem("last-read", JSON.stringify(updatedDuas)); - const slug = dua.title.toLowerCase().replaceAll(" ", "-"); - router.push(`/duas/${slug}-${dua.id}`); + const slug = dua.slug + router.push(`/duas/${slug}`); }; useEffect(() => { diff --git a/src/components/sidebar/famous.tsx b/src/components/sidebar/famous.tsx index ddbeeb5..a33013f 100644 --- a/src/components/sidebar/famous.tsx +++ b/src/components/sidebar/famous.tsx @@ -22,8 +22,8 @@ const Famous: React.FC = () => { const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]"); const updatedDuas = [...lastReadDuas, dua]; localStorage.setItem("last-read", JSON.stringify(updatedDuas)); - const slug = dua.title.toLowerCase().replaceAll(" ", "-"); - router.push(`/duas/${slug}-${dua.id}`); + const slug = dua.slug + router.push(`/duas/${slug}`); }; useEffect(() => { diff --git a/src/components/sidebar/nearby.tsx b/src/components/sidebar/nearby.tsx index a3166ba..d5e331d 100644 --- a/src/components/sidebar/nearby.tsx +++ b/src/components/sidebar/nearby.tsx @@ -19,8 +19,8 @@ const NearBy = () => { const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]"); const updatedDuas = [...lastReadDuas, dua]; localStorage.setItem("last-read", JSON.stringify(updatedDuas)); - const slug = dua.title.toLowerCase().replaceAll(" ", "-"); - router.push(`/duas/${slug}-${dua.id}`); + const slug = dua.slug + router.push(`/duas/${slug}`); }; const onClick = () => { // Check if geolocation is available diff --git a/src/components/sidebar/search.tsx b/src/components/sidebar/search.tsx index d5c2e15..2c6bab4 100644 --- a/src/components/sidebar/search.tsx +++ b/src/components/sidebar/search.tsx @@ -23,8 +23,8 @@ const Search: React.FC = () => { const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]"); const updatedDuas = [...lastReadDuas, dua]; localStorage.setItem("last-read", JSON.stringify(updatedDuas)); - const slug = dua.title.toLowerCase().replaceAll(" ", "-"); - router.push(`/duas/${slug}-${dua.id}`); + const slug = dua.slug + router.push(`/duas/${slug}`); }; useEffect(() => { diff --git a/src/components/sidebar/tabs.tsx b/src/components/sidebar/tabs.tsx index 1193236..1755802 100644 --- a/src/components/sidebar/tabs.tsx +++ b/src/components/sidebar/tabs.tsx @@ -66,13 +66,13 @@ const Tabs = () => { path.map((item, index) => { if (item.name && index + 1 < path.length && index > 0) { return ( - + handlePathClick(index)}> {item.name} - + ); } else { diff --git a/src/components/sidebar/today.tsx b/src/components/sidebar/today.tsx index 75ec2e2..dc1126a 100644 --- a/src/components/sidebar/today.tsx +++ b/src/components/sidebar/today.tsx @@ -58,8 +58,8 @@ const Today: React.FC = () => { const lastReadDuas = JSON.parse(localStorage.getItem("last-read") || "[]"); const updatedDuas = [...lastReadDuas, dua]; localStorage.setItem("last-read", JSON.stringify(updatedDuas)); - const slug = dua.title.toLowerCase().replaceAll(" ", "-"); - router.push(`/duas/${slug}-${dua.id}`); + const slug = dua.slug + router.push(`/duas/${slug}`); }; useEffect(() => { diff --git a/src/pages/404.tsx b/src/pages/404.tsx new file mode 100644 index 0000000..cf13a3c --- /dev/null +++ b/src/pages/404.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import Link from "next/link"; +import notFound from "../../public/assets/images/Icon_Illustration.webp"; +import Image from "next/image"; +import { serverSideTranslations } from "next-i18next/serverSideTranslations"; +import { useTranslation } from "next-i18next"; + +const NotFound: React.FC = () => { + const { t } = useTranslation("common"); + + return ( +
+
+ {t("notFoundImageAlt")} +

{t("ohNo")}

+

{t("somethingWentWrong")}

+
+ {t("backToHomePage")} +
+
+
+ ); +}; + +export async function getStaticProps({ locale }) { + return { + props: { + ...(await serverSideTranslations(locale, ["common", "footer"])), + // Will be passed to the page component as props + }, + }; +} + +export default NotFound; diff --git a/src/pages/duas/[slug].tsx b/src/pages/duas/[slug].tsx index becb6b8..7006170 100644 --- a/src/pages/duas/[slug].tsx +++ b/src/pages/duas/[slug].tsx @@ -15,6 +15,7 @@ import { useFontSettingsContext } from "@/components/context/font-setting-contex import { useAudio } from "@/components/context/audio-conext"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { DefaultSeo } from "next-seo"; +import NotFound from "../404"; // Define the Dua interface interface Dua { @@ -64,7 +65,7 @@ const DuaComponent: React.FC = ({ async (nextPage) => { if (!slug || fetching) return; // Prevent fetching if data is already being fetched - const id = slug.split("-").pop(); + const id = slug; if (!id) return; setFetching(true); // Set fetching to true when data starts fetching @@ -82,14 +83,17 @@ const DuaComponent: React.FC = ({ // Append the new results to the existing duaParts setDuaParts((prev) => [...prev, ...duaResponse.data.results]); + console.log(duaResponse); + getAudio(id); + } catch (error) { - console.error("Error fetching Dua parts:", error); + router.replace("/404") + console.log("Error fetching Dua parts:", error); } finally { setLoading(false); setFetching(false); // Reset fetching state after fetching is done } - getAudio(id); }, [slug, fetching, duaParts, getAudio] ); // Dependencies for fetchData @@ -237,12 +241,13 @@ const DuaComponent: React.FC = ({ }, [fetching] ); - + console.log(duaParts[0]?.seo_field); + if (!slug) { return null; // Handling the case where slug is not available } - const title = processSlug(slug); // Title derived from slug - const description = "This is a detailed description for the specific Dua page."; // You can customize this further + const title = duaParts[0]?.seo_field?.title // Title derived from slug + const description = duaParts[0]?.seo_field?.description; // You can customize this further const keywords = "dua, islam, prayer, supplication, dua parts"; // Keywords for SEO return (