Quando ho iniziato a costruire SEOJuice, la prima vera decisione non riguardava una funzionalità o il modello di prezzi. Era una domanda che continuava a tornarmi in testa: scelgo lo stack che conosco già, oppure quello che fa bella figura in un post intitolato "How We Built It"? Ho scelto la via più noiosa. E lo rifarei senza pensarci, perché una tecnologia noiosa che conosci a fondo batterà ogni singola volta una tecnologia di moda che stai ancora imparando strada facendo. (Chiedimi delle tre settimane che ho buttato a valutare un database a grafo prima di ammettere che PostgreSQL poteva fare tutto quello che mi serviva.)
Questo articolo offre una panoramica trasparente di ogni livello dell'infrastruttura di SEOJuice -- non solo delle scelte finali, ma anche dei compromessi che ci stanno dietro. Se sei un founder che sta valutando il proprio stack, spero che il ragionamento ti sia più utile delle etichette.
La mia filosofia si riduce a tre regole che ho imparato nel modo difficile, dopo anni passati a portare avanti progetti collaterali prima di SEOJuice:


Regola 1: mantieni il controllo del percorso critico. Se la funzionalità centrale del tuo prodotto dipende da un'API di terze parti, hai messo nelle mani di qualcun altro il potere di staccarti la spina. Il motore di crawling di SEOJuice, l'analisi dei link e gli algoritmi di scoring girano tutti su infrastruttura che controllo io. Nel momento in cui esternalizzi il tuo vero elemento differenziante, smetti di essere un prodotto e inizi a essere un rivenditore.
Regola 2: scegli tecnologie che sai risolvere alle 2 di notte. Ho preso in considerazione FastAPI per il back-end. Nei benchmark è più veloce, è più moderno e la gestione dell'asincronia è più lineare. Però scrivo in Django dal 2016. Quando Sentry esplode a mezzanotte, non voglio passare 40 minuti a leggere documentazione -- voglio sapere esattamente quale middleware sta creando il problema. La familiarità non è pigrizia; è maturità operativa.
Regola 3: riduci al minimo le parti in movimento. Ogni servizio in più nella tua architettura è un'altra cosa che può rompersi, un'altra cosa da monitorare, un'altra dipendenza da aggiornare. Conto i servizi nel mio file docker-compose come un escursionista conta i grammi nello zaino.
Nel cuore di SEOJuice c'è Django. L'ho scelto al posto di Flask (troppo essenziale per un prodotto di questa complessità), FastAPI (ottimo per le API, ma a me servivano l'admin di Django, l'ORM e il template engine) e Rails (non conosco Ruby abbastanza bene da metterci mano a occhi chiusi). La filosofia "batteries-included" di Django ha fatto sì che non passassi i primi tre mesi ad assemblare un sistema di autenticazione, un pannello admin e un framework per le migration mettendo insieme pacchetti separati. Il compromesso è che Django può sembrare pesante per microservizi semplici -- ma SEOJuice non è un microservizio semplice. È un monolite con più di 15 app, e Django questo lo gestisce bene.
L'applicazione gira su ASGI via Uvicorn, dietro a Nginx come proxy inverso. Sono passato da Gunicorn (WSGI) a Uvicorn circa un anno dopo, quando ho aggiunto il supporto WebSocket per le notifiche in tempo reale e il server MCP. Quella migrazione è stata sorprendentemente indolore -- uno dei vantaggi dell'adozione graduale di ASGI da parte di Django. Cloudflare sta davanti a tutta l'infrastruttura, occupandosi di DNS, terminazione SSL e protezione DDoS. Ho visto Cloudflare assorbire picchi di traffico che avrebbero messo in crisi i miei server senza nemmeno scomporsi.
Tutti i server girano su Linux ospitato da Hetzner. Perché Hetzner invece di AWS? Onestamente, il costo. Un server dedicato Hetzner con 64GB di RAM costa più o meno quanto un'istanza EC2 equivalente per una settimana. Per un prodotto autofinanziato, questa differenza conta tantissimo. Il compromesso è meno automazione -- niente auto-scaling, niente Kubernetes gestito. Gestisco i rilasci con Docker e qualche shell script. Non è glamour, ma funziona, e i soldi che risparmio finiscono direttamente nello sviluppo del prodotto.
Il database è PostgreSQL con l'estensione pgvector. All'inizio avevo valutato MongoDB (nel 2022 lo usavano tutti) e mi sono reso conto in fretta che i dati SEO hanno una struttura fortemente relazionale -- le pagine appartengono ai siti, le keyword appartengono alle pagine, i link collegano le pagine tra loro. Un database documentale mi avrebbe costretto a reimplementare metà di quello che Postgres ti dà gratis. L'estensione pgvector è stata l'elemento decisivo: mi permette di salvare e interrogare vettori di embedding direttamente accanto ai dati relazionali, alimentando il matching per similarità dei contenuti e le funzionalità di AI senza dover introdurre un database vettoriale separato.
Per i processi in background uso Celery con Redis come broker. Celery è spesso criticato nella community Python, e in parte sono critiche meritate -- la documentazione è sparsa, le opzioni di configurazione sono troppe e capire i fallimenti dei task può essere esasperante. Però non ho trovato niente di meglio per il volume di lavoro in background che SEOJuice gestisce: crawling di migliaia di pagine, analisi NLP, generazione di report, monitoraggio dei backlink. Ho valutato Dramatiq e Huey come alternative più leggere. Dramatiq è davvero valido, ma l'ecosistema di Celery (beat scheduler, flower monitoring, django-celery-results) alla fine ha vinto.
L'autenticazione passa da Auth0. Per un progetto precedente avevo costruito un sistema di autenticazione personalizzato e ho speso una quantità imbarazzante di tempo a gestire casi limite del ripristino password, limitazione delle richieste e flussi MFA. Auth0 si occupa di tutto questo. Il costo cresce con il numero di utenti, e a certe soglie si fa sentire, ma il tempo di sviluppo che fa risparmiare vale molte volte l'abbonamento.
Tutto è containerizzato con Docker. Il mio ambiente di sviluppo e quello di produzione eseguono container identici. Questo ha eliminato un'intera classe di bug "sul mio computer funziona" che tormentava i miei progetti precedenti. Sentry monitora le eccezioni in tempo reale -- posso seguire un bug segnalato da un utente dalla sua sessione fino alla riga esatta di codice in meno di un minuto.
Ho preso una decisione deliberata: evitare React, Vue e qualunque altro framework JavaScript. La dashboard di SEOJuice è HTML generato lato server con Tailwind CSS e Alpine.js per l'interattività. È la scelta su cui ricevo più critiche da altri sviluppatori, e capisco il perché -- sembra di costruire con attrezzi manuali quando esistono gli elettroutensili.
Ecco il mio ragionamento: SEOJuice è una dashboard ricca di dati. Gli utenti guardano tabelle, grafici e report. Non hanno bisogno di editing collaborativo in tempo reale o di una gestione complessa dello stato lato client. Il server-side rendering significa che ogni caricamento di pagina è veloce, il payload iniziale è piccolo e io non devo mantenere una pipeline di build front-end separata, un livello API dedicato o una libreria per la gestione dello stato. Alpine.js gestisce menu a discesa, modali e filtri interattivi. Nei pochi punti in cui mi serve un'interattività più ricca (la dashboard di monitoraggio in tempo reale), uso htmx per sostituire frammenti HTML senza ricaricare l'intera pagina.
Le risorse statiche passano attraverso la CDN di Bunny.net. Ho testato anche la CDN di Cloudflare (che già uso per il DNS), ma ho trovato il modello di prezzi per richiesta di Bunny più prevedibile per i miei pattern di traffico. La differenza è marginale -- andrebbero bene entrambe.
Una parte importante di ciò che rende SEOJuice utile è la capacità di elaborare grandi volumi di dati e usare AI per funzionalità che sarebbe impossibile costruire a mano.
Per i language model, integro OpenAI e Claude. Uso modelli diversi per compiti diversi sulla base di mesi di test: GPT-4o per l'estrazione di dati strutturati (segue gli schema JSON in modo più affidabile), Claude per l'analisi dei contenuti e i suggerimenti (scrive in modo più naturale e gestisce meglio le sfumature). Ho provato a eseguire modelli open-source in locale -- prima Llama 2, poi Llama 3 -- e il divario qualitativo per un uso in produzione era ancora troppo ampio per giustificare il costo infrastrutturale. Questo calcolo cambia ogni pochi mesi, quindi lo rivaluto regolarmente.
Uso pgvector per la ricerca sui vettori di embedding invece di un database vettoriale dedicato come Pinecone o Weaviate. Avere i vettori in PostgreSQL insieme al resto dei dati significa che posso unire i risultati di similarità degli embedding con query relazionali in una singola istruzione SQL. Le prestazioni sono adeguate per la nostra scala. Se stessi gestendo miliardi di vettori, mi servirebbe qualcosa di specializzato. A milioni, Postgres regge benissimo.
Sul lato NLP, NumPy, NLTK e Scikit-learn fanno il grosso del lavoro per estrazione di keyword, scoring dei contenuti e classificazione. Queste librerie non sono entusiasmanti, ma sono collaudate sul campo. Quando eseguo un'analisi TF-IDF su 50,000 pagine, ho bisogno che il risultato sia corretto, non innovativo.
Per il crawling di siti pesanti in JavaScript, uso Playwright in esecuzione in un container separato. Consuma più risorse rispetto a semplici richieste HTTP, ma i siti moderni spesso generano contenuti critici via JavaScript. Se il nostro crawler non li vede, non possiamo analizzarli. Playwright gestisce SPA, contenuti lazy-loaded e routing lato client che sarebbero invisibili a un crawler tradizionale.
Sull'infrastruttura di supporto sono stato molto più disposto a usare servizi di terze parti, perché nessuno di questi rappresenta un vero elemento differenziante del prodotto:
Crisp per la live chat. Ho provato prima Intercom -- troppo costoso e troppo complesso per un team di supporto composto da una sola persona. Crisp fa quello che mi serve a una frazione del costo.
Customer.io per le email. Email transazionali (ripristino password, consegna dei report), sequenze di onboarding e aggiornamenti di prodotto passano tutti da Customer.io. Sono passato da una configurazione più semplice (l'email integrata di Django con SendGrid) perché avevo bisogno di trigger comportamentali -- "invia una email con un suggerimento se l'utente non ha collegato il suo sito WordPress entro 3 giorni".
Paddle per i pagamenti. Ho iniziato con Stripe e sono migrato a Paddle come Merchant of Record. Il motivo era puramente pratico: Paddle gestisce calcolo, raccolta e versamento dell'IVA per ogni paese. Da founder europeo autofinanziato che vende a livello globale, costruire il mio sistema di conformità fiscale sarebbe stato un lavoro a tempo pieno. Paddle trattiene una quota più alta rispetto a Stripe, ma elimina un'intera categoria di carico operativo.
ChartMogul per le analytics sui ricavi. MRR, churn rate, LTV, expansion revenue -- ho bisogno che questi numeri siano accurati e si aggiornino automaticamente. Potrei calcolarli partendo dall'API di Paddle, ma ChartMogul lo fa meglio e mette in evidenza trend che probabilmente non mi verrebbe nemmeno in mente di controllare.
Per le analytics dei visitatori, eseguo un'istanza self-hosted di Plausible. Niente cookie, niente banner di consenso, niente invio dei dati dei visitatori a Google. Mi mostra sorgenti di traffico, pagine principali e referrer -- che, sinceramente, è tutto quello che mi serve. (Ho scritto di Plausible in modo più approfondito nella nostra guida agli strumenti SEO open-source.)
Nessuno stack è perfetto, e ho imparato abbastanza da sapere cosa farei diversamente se ripartissi da zero:
Investirei prima in Kubernetes. Non dal giorno uno -- per un MVP sarebbe eccessivo -- ma più o meno quando sono arrivato a 10 worker in background. Orchestrare container con shell script e systemd funziona finché funziona, e la transizione verso un'orchestrazione seria mentre stai servendo traffico in produzione mette parecchia ansia.
Separerei l'API dal monolite prima. Avere dashboard, API pubblica, API del plugin WordPress e server MCP tutti sullo stesso processo Django significa che un picco di traffico API può rallentare la dashboard. Sto estraendo gradualmente questi componenti in servizi separati, ma sarebbe stato più pulito progettarlo così fin dall'inizio.
Non cambierei invece le scelte centrali -- Django, PostgreSQL, Celery, HTML generato lato server. Hanno dimostrato il loro valore in due anni di traffico reale in produzione, e la semplicità che offrono vale più dei guadagni prestazionali che potrei ottenere con alternative più alla moda.
Come founder che lavora da solo, la trappola più pericolosa è costruire il tuo stack per il pubblico di Hacker News invece che per i requisiti reali del tuo prodotto. Ogni scelta tecnologica che ho descritto qui è nata da un'esigenza specifica, valutata rispetto alle alternative e selezionata perché mi permetteva di rilasciare più velocemente con meno sorprese. Il risultato è un sistema che posso gestire da solo, risolvere in fretta ed estendere senza dover riscrivere tutto.
Ma questo è solo l'inizio. Lo stack evolve insieme al prodotto -- sto lavorando continuamente per migliorare SEOJuice, e l'infrastruttura deve tenere il passo. Se sei un founder che sta prendendo queste stesse decisioni, spero che vedere il ragionamento dietro alle mie ti faccia risparmiare almeno una parte dei tentativi a vuoto che ho fatto io.
Continuiamo a costruire.
Un saluto,
Vadim
Letture correlate:
no credit card required