diff --git a/frontend/src/pages/TermsPage.tsx b/frontend/src/pages/TermsPage.tsx
index f1c8da3..c8336de 100644
--- a/frontend/src/pages/TermsPage.tsx
+++ b/frontend/src/pages/TermsPage.tsx
@@ -1,5 +1,6 @@
import { useTranslation } from 'react-i18next';
-import { Helmet } from 'react-helmet-async';
+import SEOHead from '@/components/seo/SEOHead';
+import { generateWebPage } from '@/utils/seo';
import { FILE_RETENTION_MINUTES } from '@/config/toolLimits';
const LAST_UPDATED = '2026-03-06';
@@ -12,11 +13,16 @@ export default function TermsPage() {
return (
<>
-
{t('pages.terms.title')}
diff --git a/frontend/src/utils/seo.ts b/frontend/src/utils/seo.ts
index 6f1acc5..9cf841a 100644
--- a/frontend/src/utils/seo.ts
+++ b/frontend/src/utils/seo.ts
@@ -67,3 +67,44 @@ export function generateFAQ(
})),
};
}
+
+/**
+ * Generate Organization JSON-LD for the site.
+ */
+export function generateOrganization(origin: string): object {
+ return {
+ '@context': 'https://schema.org',
+ '@type': 'Organization',
+ name: 'SaaS-PDF',
+ url: origin,
+ logo: `${origin}/favicon.svg`,
+ sameAs: [],
+ contactPoint: {
+ '@type': 'ContactPoint',
+ email: 'support@saas-pdf.com',
+ contactType: 'customer support',
+ availableLanguage: ['English', 'Arabic', 'French'],
+ },
+ };
+}
+
+/**
+ * Generate WebPage JSON-LD for a static page.
+ */
+export function generateWebPage(page: {
+ name: string;
+ description: string;
+ url: string;
+}): object {
+ return {
+ '@context': 'https://schema.org',
+ '@type': 'WebPage',
+ name: page.name,
+ description: page.description,
+ url: page.url,
+ isPartOf: {
+ '@type': 'WebSite',
+ name: 'SaaS-PDF',
+ },
+ };
+}
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
index a3ecdf5..6afd23b 100644
--- a/frontend/vite.config.ts
+++ b/frontend/vite.config.ts
@@ -27,11 +27,13 @@ export default defineConfig({
build: {
outDir: 'dist',
sourcemap: false,
+ cssMinify: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom', 'react-router-dom'],
i18n: ['i18next', 'react-i18next'],
+ helmet: ['react-helmet-async'],
},
},
},
diff --git a/scripts/generate_sitemap.py b/scripts/generate_sitemap.py
index 29acb9b..59a818f 100644
--- a/scripts/generate_sitemap.py
+++ b/scripts/generate_sitemap.py
@@ -1,39 +1,55 @@
#!/usr/bin/env python3
"""
generate_sitemap.py
-Generates sitemap.xml for SEO.
+Generates sitemap.xml for SEO from the full route inventory.
Usage:
python scripts/generate_sitemap.py --domain https://yourdomain.com
+ python scripts/generate_sitemap.py --domain https://yourdomain.com --output frontend/public/sitemap.xml
"""
import argparse
from datetime import datetime
-
-TOOLS = [
- '/tools/pdf-to-word',
- '/tools/word-to-pdf',
- '/tools/compress-pdf',
- '/tools/merge-pdf',
- '/tools/split-pdf',
- '/tools/rotate-pdf',
- '/tools/pdf-to-images',
- '/tools/images-to-pdf',
- '/tools/watermark-pdf',
- '/tools/protect-pdf',
- '/tools/unlock-pdf',
- '/tools/page-numbers',
- '/tools/image-converter',
- '/tools/video-to-gif',
- '/tools/word-counter',
- '/tools/text-cleaner',
-]
+# ─── Route definitions with priority and changefreq ──────────────────────────
PAGES = [
- '/',
- '/about',
- '/privacy',
+ {'path': '/', 'changefreq': 'daily', 'priority': '1.0'},
+ {'path': '/about', 'changefreq': 'monthly', 'priority': '0.4'},
+ {'path': '/contact', 'changefreq': 'monthly', 'priority': '0.4'},
+ {'path': '/privacy', 'changefreq': 'yearly', 'priority': '0.3'},
+ {'path': '/terms', 'changefreq': 'yearly', 'priority': '0.3'},
+]
+
+# PDF Tools
+PDF_TOOLS = [
+ 'pdf-to-word', 'word-to-pdf', 'compress-pdf', 'merge-pdf',
+ 'split-pdf', 'rotate-pdf', 'pdf-to-images', 'images-to-pdf',
+ 'watermark-pdf', 'remove-watermark-pdf', 'protect-pdf', 'unlock-pdf',
+ 'page-numbers', 'reorder-pdf', 'extract-pages', 'pdf-editor',
+ 'pdf-flowchart', 'pdf-to-excel',
+]
+
+# Image Tools
+IMAGE_TOOLS = [
+ 'image-converter', 'image-resize', 'compress-image', 'remove-background',
+]
+
+# AI Tools
+AI_TOOLS = [
+ 'ocr', 'chat-pdf', 'summarize-pdf', 'translate-pdf', 'extract-tables',
+]
+
+# Convert / Utility Tools
+UTILITY_TOOLS = [
+ 'html-to-pdf', 'qr-code', 'video-to-gif', 'word-counter', 'text-cleaner',
+]
+
+TOOL_GROUPS = [
+ ('PDF Tools', PDF_TOOLS, '0.9'),
+ ('Image Tools', IMAGE_TOOLS, '0.8'),
+ ('AI Tools', AI_TOOLS, '0.8'),
+ ('Utility Tools', UTILITY_TOOLS, '0.7'),
]
@@ -41,30 +57,24 @@ def generate_sitemap(domain: str) -> str:
today = datetime.now().strftime('%Y-%m-%d')
urls = []
- # Home page — highest priority
- urls.append(f'''
- {domain}/
+ # Static pages
+ for page in PAGES:
+ urls.append(f'''
+ {domain}{page["path"]}
+ {today}
+ {page["changefreq"]}
+ {page["priority"]}
+ ''')
+
+ # Tool pages by category
+ for label, slugs, priority in TOOL_GROUPS:
+ urls.append(f'\n ')
+ for slug in slugs:
+ urls.append(f'''
+ {domain}/tools/{slug}
{today}
weekly
- 1.0
- ''')
-
- # Tool pages — high priority
- for tool in TOOLS:
- urls.append(f'''
- {domain}{tool}
- {today}
- monthly
- 0.9
- ''')
-
- # Static pages — lower priority
- for page in PAGES[1:]:
- urls.append(f'''
- {domain}{page}
- {today}
- monthly
- 0.5
+ {priority}
''')
sitemap = f'''
@@ -87,8 +97,9 @@ def main():
with open(args.output, 'w', encoding='utf-8') as f:
f.write(sitemap)
+ total = len(PAGES) + sum(len(slugs) for _, slugs, _ in TOOL_GROUPS)
print(f"Sitemap generated: {args.output}")
- print(f"URLs: {len(TOOLS) + len(PAGES)}")
+ print(f"Total URLs: {total}")
if __name__ == '__main__':