Dev.to AI 🤖 Ai 👁 0 📖 2 min read

How I Built an AI SEO Automation SaaS for PrestaShop — Architecture & Lessons Learned

I run a small SaaS called Fexa AI — it automates SEO content generation and multilingual translation for PrestaShop stores. This is what I learned building it. The problem PrestaShop merchants have catalogs of hundreds

How I Built an AI SEO Automation SaaS for PrestaShop — Architecture & Lessons Learned

I run a small SaaS called Fexa AI — it automates SEO content generation and multilingual translation for PrestaShop stores. This is what I learned building it.


The problem
PrestaShop merchants have catalogs of hundreds (sometimes thousands) of products. Every product needs a unique title tag, meta description, ALT text, and structured data. Multiply by 4 languages = tens of thousands of manual tasks. Most merchants just skip it. Their SEO suffers. I wanted to automate this.

Stack
Frontend: Next.js 14 App Router + TypeScript
Backend: REST APIs + Stripe webhooks, Prisma ORM on PostgreSQL (Neon)
AI: Vercel AI SDK → DeepSeek with Google Gemini as auto-fallback
Queue: Google Cloud Tasks for async bulk jobs
Infra: Google Cloud Run (europe-west1)
Store integration: PrestaShop WebService API via a free open-source module
The hardest problem: Cloud Run timeouts
Cloud Run has a 60-second request timeout. Translating a 3,000-product catalog in one HTTP request is a guaranteed timeout.

My fix: chunking via Cloud Tasks.

Instead of processing everything in one request, the API endpoint:

Splits the job into chunks of ~50 products
Enqueues one Cloud Task per chunk
Each task hits a separate endpoint, processes its chunk, and exits
Key detail: idempotency. Cloud Tasks can retry on failure. Without an idempotency key per chunk, a retry would re-bill the user's credits. I store a jobChunkId in the DB — if a task arrives twice, the second one returns 200 immediately.

Credit atomicity
Each AI generation costs credits. With parallel tasks running on the same account, you can race-condition your way into negative credits.

Solution: PostgreSQL atomic decrement with a floor check — if the user doesn't have enough credits, the row update returns empty and the job is rejected before any AI call is made.

AI consistency across 4 languages
The naive approach (generate in French, then translate) produces poor results — Spanish SEO copy translated from French reads awkwardly.

Better: send the original product data directly to the model with a locale-specific system prompt. Each language gets its own generation, not a translation of a translation.

Stripe webhook reliability
Stripe can replay webhooks. Without deduplication, a replayed checkout.session.completed would grant credits twice.

Fix: store each stripeEvent.id in a unique DB table before processing. If it's already there, return 200 immediately.

What I'd do differently
Start with chunking — I built the single-request version first and had to refactor everything under production pressure.
Idempotency from day one — retrofitting it is painful.
Separate the AI layer — mixing AI orchestration with business logic in the same route handlers makes testing hard.
Result
One merchant translated and re-optimized 3,500 product pages in under 4 hours. That's the kind of result that makes the architecture complexity worth it.

If you're building something similar (AI + async queues + Stripe), happy to answer questions in the comments.

→ fexaai.com — free account, no credit card required.

📰 Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.