Add Plausible and Google Site Verification support to analytics and environment configuration

This commit is contained in:
Your Name
2026-03-10 10:47:43 +02:00
parent 2d53c8df97
commit d5e6df42d3
4 changed files with 334 additions and 16 deletions

View File

@@ -28,6 +28,9 @@ CORS_ORIGINS=http://localhost:5173,http://localhost:3000
# Frontend Analytics / Ads (Vite) # Frontend Analytics / Ads (Vite)
VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX 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_CLIENT_ID=ca-pub-XXXXXXXXXXXXXXXX
VITE_ADSENSE_SLOT_HOME_TOP=1234567890 VITE_ADSENSE_SLOT_HOME_TOP=1234567890
VITE_ADSENSE_SLOT_HOME_BOTTOM=1234567891 VITE_ADSENSE_SLOT_HOME_BOTTOM=1234567891

252
docs/seo_strategy.md Normal file
View File

@@ -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 `<link rel="canonical">` 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 `<meta name="google-site-verification">` 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 35 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 12) — 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 36) — Target: 30K visits/month
**Blog / Resource Pages:**
- [ ] Create `/blog` section with 24 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 200500 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 612) — 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 1218) — 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 816 blog posts (24/week × 3 languages)
□ Build 510 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.

View File

@@ -1,4 +1,7 @@
VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX 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_CLIENT_ID=ca-pub-XXXXXXXXXXXXXXXX
VITE_ADSENSE_SLOT_HOME_TOP=1234567890 VITE_ADSENSE_SLOT_HOME_TOP=1234567890
VITE_ADSENSE_SLOT_HOME_BOTTOM=1234567891 VITE_ADSENSE_SLOT_HOME_BOTTOM=1234567891

View File

@@ -4,12 +4,17 @@ declare global {
interface Window { interface Window {
dataLayer: unknown[]; dataLayer: unknown[];
gtag?: (...args: unknown[]) => void; gtag?: (...args: unknown[]) => void;
plausible?: (event: string, opts?: { props?: Record<string, string> }) => void;
} }
} }
const GA_MEASUREMENT_ID = (import.meta.env.VITE_GA_MEASUREMENT_ID || '').trim(); 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; let initialized = false;
// ─── Google Analytics ────────────────────────────────────────────
function ensureGtagShim() { function ensureGtagShim() {
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
window.gtag = window.gtag =
@@ -34,34 +39,89 @@ function loadGaScript() {
document.head.appendChild(script); document.head.appendChild(script);
} }
export function initAnalytics() { // ─── Plausible Analytics ─────────────────────────────────────────
if (initialized || !GA_MEASUREMENT_ID || typeof window === 'undefined') return;
function loadPlausibleScript() {
if (!PLAUSIBLE_DOMAIN) return;
const existing = document.querySelector<HTMLScriptElement>('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(); ensureGtagShim();
loadGaScript(); loadGaScript();
window.gtag?.('js', new Date()); window.gtag?.('js', new Date());
window.gtag?.('config', GA_MEASUREMENT_ID, { send_page_view: false }); window.gtag?.('config', GA_MEASUREMENT_ID, { send_page_view: false });
}
// Plausible
loadPlausibleScript();
// Search Console
injectSearchConsoleVerification();
initialized = true; initialized = true;
} }
export function trackPageView(path: string) { export function trackPageView(path: string) {
if (!initialized || !window.gtag) return; // GA4
if (window.gtag) {
window.gtag('event', 'page_view', { window.gtag('event', 'page_view', {
page_path: path, page_path: path,
page_location: `${window.location.origin}${path}`, page_location: `${window.location.origin}${path}`,
page_title: document.title, page_title: document.title,
}); });
}
// Plausible tracks page views automatically via its script
} }
export function trackEvent( export function trackEvent(
eventName: string, eventName: string,
params: Record<string, AnalyticsValue> = {} params: Record<string, AnalyticsValue> = {}
) { ) {
if (!initialized || !window.gtag) return; // GA4
if (window.gtag) {
window.gtag('event', eventName, params); window.gtag('event', eventName, params);
}
// Plausible custom event
if (window.plausible) {
const props: Record<string, string> = {};
for (const [k, v] of Object.entries(params)) {
if (v !== undefined) props[k] = String(v);
}
window.plausible(eventName, { props });
}
} }
export function analyticsEnabled() { export function analyticsEnabled() {
return Boolean(GA_MEASUREMENT_ID); return Boolean(GA_MEASUREMENT_ID) || Boolean(PLAUSIBLE_DOMAIN);
} }