diff --git a/.env.example b/.env.example
index 9ed3769..62abcc7 100644
--- a/.env.example
+++ b/.env.example
@@ -28,6 +28,9 @@ CORS_ORIGINS=http://localhost:5173,http://localhost:3000
# Frontend Analytics / Ads (Vite)
VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX
+VITE_PLAUSIBLE_DOMAIN=
+VITE_PLAUSIBLE_SRC=https://plausible.io/js/script.js
+VITE_GOOGLE_SITE_VERIFICATION=
VITE_ADSENSE_CLIENT_ID=ca-pub-XXXXXXXXXXXXXXXX
VITE_ADSENSE_SLOT_HOME_TOP=1234567890
VITE_ADSENSE_SLOT_HOME_BOTTOM=1234567891
diff --git a/docs/seo_strategy.md b/docs/seo_strategy.md
new file mode 100644
index 0000000..a6079ea
--- /dev/null
+++ b/docs/seo_strategy.md
@@ -0,0 +1,252 @@
+# SaaS-PDF — SEO & Growth Strategy
+
+> Roadmap to **500 000 monthly visits** for a multilingual (EN / AR / FR) free-tool SaaS.
+
+---
+
+## 1. Current Technical SEO Foundation
+
+| Layer | Status |
+|-------|--------|
+| **Canonical URLs** | Every page emits `` via `SEOHead` |
+| **OpenGraph tags** | `og:title`, `og:description`, `og:url`, `og:type`, `og:site_name`, `og:locale` on all pages |
+| **Twitter cards** | `twitter:card`, `twitter:title`, `twitter:description` on all pages |
+| **Structured data** | `WebSite`, `Organization`, `WebPage`, `WebApplication`, `BreadcrumbList`, `FAQPage` JSON-LD |
+| **Sitemap** | Auto-generated via `scripts/generate_sitemap.py` — 37 URLs (5 pages + 32 tools) |
+| **robots.txt** | Allows all crawlers; blocks `/api/`, `/account`, auth pages |
+| **Internationalization** | Full i18n in EN, AR, FR — all tool pages, SEO content, and static pages |
+| **Font loading** | `dns-prefetch` + `preconnect` + `display=swap` for Google Fonts |
+| **Analytics** | GA4 (opt-in via `VITE_GA_MEASUREMENT_ID`) + Plausible (opt-in via `VITE_PLAUSIBLE_DOMAIN`) |
+| **Search Console** | Verification via `VITE_GOOGLE_SITE_VERIFICATION` meta tag |
+| **Page speed** | Code-split (lazy routes), Vite manual chunks, CSS minification, nginx gzip |
+
+---
+
+## 2. Analytics Setup
+
+### Google Analytics 4
+
+```env
+VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX
+```
+
+- Auto-loaded via `initAnalytics()` on first render
+- Page views tracked on every route change
+- Custom events via `trackEvent('tool_used', { tool: 'compress-pdf' })`
+
+### Plausible (Privacy-Friendly Alternative)
+
+```env
+VITE_PLAUSIBLE_DOMAIN=saas-pdf.com
+VITE_PLAUSIBLE_SRC=https://plausible.io/js/script.js # or self-hosted URL
+```
+
+- Lightweight (< 1 KB), no cookies, GDPR-compliant
+- Runs alongside or instead of GA4 — both are opt-in
+- Custom events forwarded to Plausible automatically
+
+### Google Search Console
+
+```env
+VITE_GOOGLE_SITE_VERIFICATION=your-verification-code
+```
+
+- Injected as `` at runtime
+- Enables index coverage, search performance, and Core Web Vitals reporting
+
+---
+
+## 3. SEO Content Architecture
+
+### 3.1 Tool Landing Pages (32 pages)
+
+Each tool page (`/tools/{slug}`) renders via `ToolLandingPage` wrapper:
+
+1. **Helmet** — title, meta description, keywords, canonical, OG, Twitter
+2. **Tool UI** — upload zone, processing, download
+3. **What it does** — descriptive paragraph
+4. **How to use** — ordered steps (4 items)
+5. **Benefits** — bullet list (5 items)
+6. **Common use cases** — bullet list (5 items)
+7. **FAQ** — accordion with 3–5 Q&A pairs (generates `FAQPage` schema)
+8. **Related tools** — internal link grid (4 tools)
+
+All text is i18n-driven from `seo.{toolKey}.*` keys in EN/AR/FR.
+
+### 3.2 Static Pages (5 pages)
+
+| Path | Schema | Purpose |
+|------|--------|---------|
+| `/` | `WebSite` + `Organization` | Homepage with hero + tool grid |
+| `/about` | `WebPage` | Mission, technology, security |
+| `/contact` | `WebPage` | Contact form (mailto-based) |
+| `/privacy` | `WebPage` | Privacy policy |
+| `/terms` | `WebPage` | Terms of service |
+
+### 3.3 SEO Support Files
+
+| File | Purpose |
+|------|---------|
+| `sitemap.xml` | All 37 indexable URLs with priority and changefreq |
+| `robots.txt` | Crawler directives + sitemap pointer |
+| `llms.txt` | AI/LLM discoverability file |
+| `humans.txt` | Team credits |
+
+---
+
+## 4. Growth Playbook — Path to 500K Visits/Month
+
+### Phase A: Foundation (Month 1–2) — Target: 5K visits/month
+
+**Technical:**
+- [ ] Deploy to production with real domain
+- [ ] Submit sitemap to Google Search Console and Bing Webmaster Tools
+- [ ] Run Lighthouse audits → fix any issues below 90 score
+- [ ] Set up GA4 + Plausible dashboards
+- [ ] Verify Core Web Vitals pass (LCP < 2.5s, FID < 100ms, CLS < 0.1)
+
+**Content:**
+- [ ] Publish all 32 tool pages with full SEO content
+- [ ] Ensure hreflang tags work across EN/AR/FR (add `hreflang` links if using subdomains or subdirectories)
+- [ ] Add FAQ schema to all tool pages (already done)
+
+**Indexing:**
+- [ ] Request indexing for top 10 highest-priority tool pages via Search Console
+- [ ] Monitor index coverage weekly
+
+---
+
+### Phase B: Content Marketing (Month 3–6) — Target: 30K visits/month
+
+**Blog / Resource Pages:**
+- [ ] Create `/blog` section with 2–4 articles per week
+- [ ] Target long-tail keywords per tool:
+ - "how to compress PDF without losing quality"
+ - "convert PDF to Word free online"
+ - "merge multiple PDFs into one"
+ - "كيفية دمج ملفات PDF" (Arabic equivalent)
+ - "comment fusionner des fichiers PDF" (French equivalent)
+- [ ] Each blog post links to the relevant tool page (internal linking)
+- [ ] Create comparison pages: "SaaS-PDF vs iLovePDF vs SmallPDF"
+
+**Keyword Research Strategy:**
+- Target 200–500 keywords across three tiers:
+ - **Head terms** (high volume, high competition): "PDF converter", "merge PDF" — target via homepage + tool pages
+ - **Mid-tail** (medium volume): "compress PDF to 1MB", "PDF to Word with formatting" — target via tool pages + blog
+ - **Long-tail** (low volume, low competition): "how to remove watermark from PDF free", "convert scanned PDF to text" — target via blog articles
+
+**Multilingual Scale:**
+- Every blog post published in EN, AR, and FR simultaneously
+- Arabic content is underserved in the PDF tools niche — a major competitive advantage
+- Target 50+ Arabic long-tail keywords with almost zero competition
+
+---
+
+### Phase C: Authority Building (Month 6–12) — Target: 100K visits/month
+
+**Link Building:**
+- [ ] Submit to web tool directories (Product Hunt, AlternativeTo, ToolFinder)
+- [ ] Create free embeddable widgets (PDF page counter, file size estimator)
+- [ ] Write guest posts on productivity and SaaS blogs
+- [ ] Build a "Free PDF Tools" resource page that other sites want to link to
+- [ ] Reach out to educational institutions (free tools for students = .edu backlinks)
+
+**Technical Improvements:**
+- [ ] Implement `hreflang` for multilingual SEO (subdirectories: `/en/`, `/ar/`, `/fr/`)
+- [ ] Add breadcrumb navigation to tool pages
+- [ ] Create topic clusters: PDF Tools Hub → individual tool pages
+- [ ] Implement internal search with search analytics
+
+**Social Proof:**
+- [ ] Add user count ("X files processed this month") to homepage
+- [ ] Collect and display testimonials
+- [ ] Create YouTube tutorials for each tool (video SEO)
+
+---
+
+### Phase D: Scale & Monetize (Month 12–18) — Target: 500K visits/month
+
+**Content Flywheel:**
+- [ ] 100+ blog posts across 3 languages (300+ total pages)
+- [ ] Programmatic SEO: auto-generate pages for format combinations
+ - "/convert/pdf-to-jpg", "/convert/docx-to-pdf", "/convert/png-to-webp"
+ - Each page targets a specific keyword with unique content
+- [ ] Create glossary pages: "What is OCR?", "What is PDF/A?"
+- [ ] Build an API documentation page (drives developer traffic)
+
+**Distribution Channels:**
+- [ ] Email newsletter with PDF tips (capture leads via tool pages)
+- [ ] Social media presence: Twitter/X, LinkedIn, Reddit (r/pdf, r/productivity)
+- [ ] Quora/Stack Overflow answers linking back to tools
+- [ ] YouTube shorts demonstrating each tool (< 60s)
+
+**Conversion Optimization:**
+- [ ] A/B test hero copy, CTA buttons, tool card layouts
+- [ ] Add "suggested next tool" after file processing
+- [ ] Implement PWA for repeat visits (offline capability)
+
+**Performance Monitoring:**
+- Key metrics to track weekly:
+ - Organic sessions (GA4/Plausible)
+ - Indexed pages (Search Console)
+ - Average position for target keywords
+ - Click-through rate (CTR) from SERPs
+ - Pages per session / bounce rate
+ - Core Web Vitals scores
+ - Backlink count (Ahrefs/Moz)
+
+---
+
+## 5. Competitive Analysis
+
+| Competitor | Monthly Traffic | Strengths | Our Advantage |
+|-----------|----------------|-----------|---------------|
+| iLovePDF | ~150M | Brand recognition, wide tool set | Arabic/French i18n, free with no limits |
+| SmallPDF | ~60M | UX polish, enterprise features | No signup required, fully free |
+| PDF24 | ~40M | Desktop app + web tools | Lightweight, faster load, mobile-first |
+| Sejda | ~10M | Advanced editing features | More tools, trilingual content |
+
+**Key differentiators for SaaS-PDF:**
+1. **Trilingual** — EN/AR/FR from day one (Arabic market is largely unserved)
+2. **No signup** — zero friction, instant file processing
+3. **32 tools** — broader coverage than most competitors
+4. **AI-powered tools** — OCR, Chat PDF, Summarize, Translate (unique value)
+5. **Privacy-first** — files auto-deleted within 30 minutes
+
+---
+
+## 6. Monthly SEO Checklist
+
+```
+□ Review Search Console for crawl errors and fix immediately
+□ Check index coverage — ensure all 37+ pages are indexed
+□ Review top queries — identify rising keywords to create content for
+□ Publish 8–16 blog posts (2–4/week × 3 languages)
+□ Build 5–10 backlinks through outreach
+□ Run Lighthouse audit — maintain 90+ scores
+□ Update sitemap if new pages were added
+□ Monitor Core Web Vitals — fix any regressions
+□ Review analytics dashboards — identify underperforming pages
+□ Competitor check — new features or content gaps to exploit
+```
+
+---
+
+## 7. Environment Variables Reference
+
+```env
+# Google Analytics 4 (optional)
+VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX
+
+# Plausible Analytics (optional, privacy-friendly)
+VITE_PLAUSIBLE_DOMAIN=saas-pdf.com
+VITE_PLAUSIBLE_SRC=https://plausible.io/js/script.js
+
+# Google Search Console verification (optional)
+VITE_GOOGLE_SITE_VERIFICATION=your-verification-code
+
+# Google AdSense (optional)
+VITE_ADSENSE_CLIENT_ID=ca-pub-XXXXXXXXXXXXXXXX
+```
+
+All integrations are **opt-in** — if the env var is empty or unset, the corresponding script is not loaded, keeping the bundle clean for development.
diff --git a/frontend/.env.example b/frontend/.env.example
index c983eb1..f904a6e 100644
--- a/frontend/.env.example
+++ b/frontend/.env.example
@@ -1,4 +1,7 @@
VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX
+VITE_PLAUSIBLE_DOMAIN=
+VITE_PLAUSIBLE_SRC=https://plausible.io/js/script.js
+VITE_GOOGLE_SITE_VERIFICATION=
VITE_ADSENSE_CLIENT_ID=ca-pub-XXXXXXXXXXXXXXXX
VITE_ADSENSE_SLOT_HOME_TOP=1234567890
VITE_ADSENSE_SLOT_HOME_BOTTOM=1234567891
diff --git a/frontend/src/services/analytics.ts b/frontend/src/services/analytics.ts
index 3211ea3..0256aea 100644
--- a/frontend/src/services/analytics.ts
+++ b/frontend/src/services/analytics.ts
@@ -4,12 +4,17 @@ declare global {
interface Window {
dataLayer: unknown[];
gtag?: (...args: unknown[]) => void;
+ plausible?: (event: string, opts?: { props?: Record }) => void;
}
}
const GA_MEASUREMENT_ID = (import.meta.env.VITE_GA_MEASUREMENT_ID || '').trim();
+const PLAUSIBLE_DOMAIN = (import.meta.env.VITE_PLAUSIBLE_DOMAIN || '').trim();
+const PLAUSIBLE_SRC = (import.meta.env.VITE_PLAUSIBLE_SRC || 'https://plausible.io/js/script.js').trim();
let initialized = false;
+// ─── Google Analytics ────────────────────────────────────────────
+
function ensureGtagShim() {
window.dataLayer = window.dataLayer || [];
window.gtag =
@@ -34,34 +39,89 @@ function loadGaScript() {
document.head.appendChild(script);
}
-export function initAnalytics() {
- if (initialized || !GA_MEASUREMENT_ID || typeof window === 'undefined') return;
+// ─── Plausible Analytics ─────────────────────────────────────────
+
+function loadPlausibleScript() {
+ if (!PLAUSIBLE_DOMAIN) return;
+
+ const existing = document.querySelector('script[data-plausible]');
+ if (existing) return;
+
+ const script = document.createElement('script');
+ script.defer = true;
+ script.setAttribute('data-domain', PLAUSIBLE_DOMAIN);
+ script.setAttribute('data-plausible', '');
+ script.src = PLAUSIBLE_SRC;
+ document.head.appendChild(script);
+}
+
+// ─── Search Console verification ─────────────────────────────────
+
+function injectSearchConsoleVerification() {
+ const code = (import.meta.env.VITE_GOOGLE_SITE_VERIFICATION || '').trim();
+ if (!code) return;
+
+ const existing = document.querySelector('meta[name="google-site-verification"]');
+ if (existing) return;
+
+ const meta = document.createElement('meta');
+ meta.name = 'google-site-verification';
+ meta.content = code;
+ document.head.appendChild(meta);
+}
+
+// ─── Public API ──────────────────────────────────────────────────
+
+export function initAnalytics() {
+ if (initialized || typeof window === 'undefined') return;
+
+ // Google Analytics
+ if (GA_MEASUREMENT_ID) {
+ ensureGtagShim();
+ loadGaScript();
+ window.gtag?.('js', new Date());
+ window.gtag?.('config', GA_MEASUREMENT_ID, { send_page_view: false });
+ }
+
+ // Plausible
+ loadPlausibleScript();
+
+ // Search Console
+ injectSearchConsoleVerification();
- ensureGtagShim();
- loadGaScript();
- window.gtag?.('js', new Date());
- window.gtag?.('config', GA_MEASUREMENT_ID, { send_page_view: false });
initialized = true;
}
export function trackPageView(path: string) {
- if (!initialized || !window.gtag) return;
-
- window.gtag('event', 'page_view', {
- page_path: path,
- page_location: `${window.location.origin}${path}`,
- page_title: document.title,
- });
+ // GA4
+ if (window.gtag) {
+ window.gtag('event', 'page_view', {
+ page_path: path,
+ page_location: `${window.location.origin}${path}`,
+ page_title: document.title,
+ });
+ }
+ // Plausible tracks page views automatically via its script
}
export function trackEvent(
eventName: string,
params: Record = {}
) {
- if (!initialized || !window.gtag) return;
- window.gtag('event', eventName, params);
+ // GA4
+ if (window.gtag) {
+ window.gtag('event', eventName, params);
+ }
+ // Plausible custom event
+ if (window.plausible) {
+ const props: Record = {};
+ for (const [k, v] of Object.entries(params)) {
+ if (v !== undefined) props[k] = String(v);
+ }
+ window.plausible(eventName, { props });
+ }
}
export function analyticsEnabled() {
- return Boolean(GA_MEASUREMENT_ID);
+ return Boolean(GA_MEASUREMENT_ID) || Boolean(PLAUSIBLE_DOMAIN);
}