{"slug": "industrial-seo-at-100-pages-week-my-n8n-claude-code-rag-stack", "title": "Industrial SEO at 100 Pages/Week: My n8n + Claude Code + RAG Stack", "summary": "A French SEO agency operating from Siem Reap, Cambodia, has shipped over 1,300 semantic content clusters for 650+ brands at a pace of 50 to 100 pages per week using a three-layer automation pipeline. The stack combines a per-client RAG knowledge base, n8n orchestration, and a Claude Code QA loop to maintain cluster coherence across hundreds of pages, solving the problem of content drift that plagues raw LLM generation.", "body_md": "I run a French SEO agency from Siem Reap, Cambodia. We've shipped 1,300+ semantic content clusters for 650+ brands — typically at 50 to 100 pages per project per week.\n\nThat cadence is impossible with a traditional content team. It's also impossible with raw LLM generation: the output looks fine in isolation and rots when you read three pages in a row.\n\nWhat works is a three-layer pipeline that treats content like a production line, not a creative process. Here's the actual stack.\n\nMost \"industrial AI content\" implementations look like this:\n\n```\nkeyword list → ChatGPT prompt → publish\n```\n\nIt produces 50 pages in an hour. It also produces 50 pages that:\n\nGoogle's May 2024 leak confirmed what experienced SEOs already knew: the algorithm scores the cluster, not just the page. `siteFocusScore`\n\n, `siteAuthority`\n\n, and the compressed quality signals don't care that page 23 is well-written if pages 1 through 22 read like the same prompt with different keywords.\n\nSo the question isn't \"how do I generate content fast?\" It's \"how do I generate content fast AND keep it coherent across the whole cluster?\"\n\n```\n┌────────────────────────────────────────────────────┐\n│  Layer 1 — RAG knowledge base (per-client)         │\n│  - Client brief, brand voice, product corpus       │\n│  - Existing pages (what's already said)            │\n│  - Topic graph (what each page must cover)         │\n└────────────────────────────────────────────────────┘\n                       ▼\n┌────────────────────────────────────────────────────┐\n│  Layer 2 — n8n orchestration                       │\n│  - Pull next page brief from Google Sheet          │\n│  - Inject RAG context + brief                      │\n│  - Call LLM (Claude/DeepSeek depending on tier)    │\n│  - Save draft to Sheet column                      │\n│  - Trigger QA round                                │\n└────────────────────────────────────────────────────┘\n                       ▼\n┌────────────────────────────────────────────────────┐\n│  Layer 3 — Claude Code QA loop                     │\n│  - Read draft + cluster context                    │\n│  - Check coherence, internal links, brand voice    │\n│  - Either approve or write structured feedback     │\n│  - Loop until pass or human escalation             │\n└────────────────────────────────────────────────────┘\n```\n\nThe trick is that Layer 1 is what makes Layer 2's output not boring, and Layer 3 is what catches Layer 2's mistakes before a human ever reads them.\n\nOne per client. Indexed and re-indexed on every brief update. Stored as a local vector DB (we use `qdrant`\n\nfor production, `chromadb`\n\nfor prototyping) with three collections:\n\n```\n# Pseudo-structure (sanitized from production)\ncollections = {\n    \"client_brief\": {\n        \"docs\": [\"positioning.md\", \"tone_of_voice.md\", \"products/*.md\"],\n        \"chunk_size\": 512,\n    },\n    \"existing_pages\": {\n        \"docs\": [\"site_pages/*.html\"],  # cleaned + extracted\n        \"chunk_size\": 1024,\n    },\n    \"topic_graph\": {\n        \"docs\": [\"cluster_map.json\"],   # which page covers which subtopic\n        \"chunk_size\": 256,\n    },\n}\n```\n\nThe retrieval at generation time pulls top-k from each collection, with weights tuned per cluster. Typical pull for one page:\n\n`client_brief`\n\n(voice + positioning)`existing_pages`\n\n(so we don't repeat what's already said)`topic_graph`\n\n(what THIS page must cover that others don't)That last one is what kills cluster drift. Without it, page 30 will accidentally rewrite page 4.\n\nn8n is the right tool because the loop has too many side effects to keep in a Python script: Google Sheets read/write, LLM API calls with retry logic, conditional branching on tier, webhook callbacks from Layer 3, Slack notifications when something stalls.\n\nThe core loop, simplified:\n\n```\n[Trigger: cron every 10min]\n    │\n    ▼\n[Google Sheets: get next row WHERE status = \"to_write\"]\n    │\n    ▼\n[HTTP: call RAG service, get context]\n    │\n    ▼\n[Switch by tier]\n    ├─ premium → Claude Sonnet\n    ├─ standard → DeepSeek Pro\n    └─ longtail → DeepSeek Flash\n    │\n    ▼\n[LLM call with composed prompt]\n    │\n    ▼\n[Google Sheets: update row with draft + status = \"to_qa\"]\n    │\n    ▼\n[Webhook: trigger Layer 3]\n```\n\nThe Switch node by tier is what makes the unit economics work. A premium page costs ~$0.40 in API spend; a long-tail page costs ~$0.02. You can't ship 100 pages/week on premium pricing for every page.\n\nn8n's other quiet superpower: error workflows. Every node in the production graph has an error handler that writes to a \"stuck\" sheet with the error message and stack. A human reads that sheet once a day. Anything not in the sheet just worked.\n\nThis is the layer most people don't have, and it's the one that decides whether the cluster is shippable or another low-quality generative blob.\n\nI use Claude Code (the CLI, not the API directly) because the agentic loop is built in. The QA agent runs against each draft:\n\n```\nclaude --model claude-sonnet-4-6 --dangerously-skip-permissions \\\n  \"Read the draft at $DRAFT_PATH. Read the cluster context at $CONTEXT_PATH.\n   Run the checks defined in qa-rules.md.\n   For each failed check, write structured feedback to $FEEDBACK_PATH.\n   If all checks pass, write APPROVED to $STATUS_PATH and exit.\n   If 3+ checks fail, write ESCALATE to $STATUS_PATH and exit.\"\n```\n\nThe `qa-rules.md`\n\nfile is the contract. It includes things like:\n\n```\n- Voice: matches the tone defined in client_brief/tone_of_voice.md\n- Repetition: no intro paragraph that mirrors another page in this cluster\n- Internal links: 3-5 contextual links to sibling pages in the cluster\n- Claims: every statistic must trace to a source in client_brief/ or be removed\n- Hooks: opening sentence must not be a generic platitude\n- Tail: closing sentence must not be a CTA — that's the layout's job\n```\n\nWhen the agent writes structured feedback, n8n picks it up via webhook and routes the draft back to Layer 2 with the feedback injected into the next prompt. Three rounds, then human review.\n\nThe economics: ~70% of drafts pass on round 1, ~25% on round 2, ~5% need human eyes. That last 5% is where the real attention goes.\n\nA few things that cost us months to learn:\n\n**1. Don't index the entire client site into the RAG store on day 1.** Index the brief + 10 hand-picked pages first. When generation starts producing \"voice drift,\" then add more pages. Indexing too early means the retrieval pulls in stale content as \"the voice.\"\n\n**2. Don't put cluster theme detection in the LLM.** Encode it in the topic graph as structured metadata. The LLM is bad at remembering \"this page is about X, not Y\" across 50 turns; the topic graph never forgets.\n\n**3. Add a fingerprint check on intros.** Cheap: hash the first 50 words of every published page in the cluster. New drafts get compared. If hamming distance is below threshold, regenerate the intro. This single check killed the repetition problem in one afternoon.\n\n**4. Don't trust the LLM's self-evaluation in the same call.** Two-call evaluation (generate, then a fresh model instance evaluates) catches things a single call misses. Same model, fresh context, no anchoring bias.\n\n**5. Keep a kill switch on every workflow.** A single Sheet cell named `pipeline.enabled`\n\n. If it's `FALSE`\n\n, every n8n trigger short-circuits. You will need it.\n\nThis pipeline is not \"AI writing the content.\" It's a content production line where:\n\nYou can't skip those four human inputs and expect this to work. The pipeline ships 100 pages/week because the upstream work is done. Without it, you ship 100 pages of slop.\n\nThe other trade-off: this is a system, not a tool. It needs maintenance — RAG re-indexing as the client's catalog evolves, prompt tuning as Claude/DeepSeek versions change, kill switch monitoring. Budget roughly 1 senior engineer day per week per active project.\n\nI'm publishing the topic graph schema, the QA rules template, and a sanitized version of the n8n workflow as a separate repo. Drop a comment if you'd find that useful.\n\nThe same pipeline, with a different QA layer that checks LLM citability instead of cluster coherence, is what we use for GEO (Generative Engine Optimization). That's the next post in this series.\n\n*Stéphane Jambu — SEO engineer building topical authority at scale. 1,300+ semantic clusters for 650+ brands. Speaking on industrial SEO at stephane-jambu.com.*", "url": "https://wpnews.pro/news/industrial-seo-at-100-pages-week-my-n8n-claude-code-rag-stack", "canonical_source": "https://dev.to/stephanejambu/industrial-seo-at-100-pagesweek-my-n8n-claude-code-rag-stack-2k58", "published_at": "2026-05-27 04:51:53+00:00", "updated_at": "2026-05-27 04:52:21.339721+00:00", "lang": "en", "topics": ["artificial-intelligence", "generative-ai", "ai-tools", "natural-language-processing", "ai-infrastructure"], "entities": ["Google", "n8n", "Claude Code", "SEO", "Siem Reap", "ChatGPT"], "alternates": {"html": "https://wpnews.pro/news/industrial-seo-at-100-pages-week-my-n8n-claude-code-rag-stack", "markdown": "https://wpnews.pro/news/industrial-seo-at-100-pages-week-my-n8n-claude-code-rag-stack.md", "text": "https://wpnews.pro/news/industrial-seo-at-100-pages-week-my-n8n-claude-code-rag-stack.txt", "jsonld": "https://wpnews.pro/news/industrial-seo-at-100-pages-week-my-n8n-claude-code-rag-stack.jsonld"}}