Concevoir et gérer une base de données optimisée
Gestion de contenu dynamique, automatisation et passage à l'échelle.

C’est la base de données qui décide si un site programmatique mérite d’être indexé. Le template n’affiche que ce que vous avez stocké : si deux lignes ne diffèrent que par un nom de ville, aucun design ne sauvera ces deux pages. Cherchez d’abord ce qui rend chaque page utile, modélisez-le, et choisissez l’outil en dernier.
Pour ce chapitre, prenons un annuaire d’espaces de coworking couvrant 2 000 villes françaises, avec une page par ville.
Partez de la page pour remonter aux colonnes
Reprenez votre zoning de page et demandez-vous d’où vient le contenu de chaque zone. Il n’y a que trois réponses possibles : un fait stocké, un calcul à partir de faits stockés, ou un texte généré.
| Zone de la page | Champ | Origine |
|---|---|---|
| Balise title et H1 | city.name, nombre d’espaces |
Calculé |
| Paragraphe d’intro | city.intro_text |
Généré, puis relu |
| Liste des espaces | space.name, space.day_pass_eur |
Stocké |
| Synthèse des prix | Pass journée le moins cher et médian | Calculé |
| Villes proches | city_link |
Relation stockée |
| JSON-LD | space.name, space.address |
Calculé |
Ce sont les lignes calculées que je pousse le plus. « Le pass journée le moins cher de cette ville coûte X, et la plupart des espaces avec salles de réunion facturent Y » : une page ne peut porter cette information que si la donnée existe. Et cela distingue une page de la suivante bien mieux qu’un paragraphe reformulé.
Soyez granulaire. Chez Deux.io, j’ai construit plus de 90 variables, chacune étant un élément qui rend la page unique : images, titres, métadonnées, schémas, profils mis en avant, propositions de valeur, articles associés, liens. Selon la variable, cela représentait de 100 à plus de 2 500 déclinaisons.
Modélisez ce qui existe, puis les pages
L’erreur classique, c’est le tableur géant avec une ligne par page, des colonnes link_1 à link_5 et le même prix collé à trois endroits. Cela tient pour 50 pages. Au-delà, chaque correction devient un rechercher-remplacer.
Donnez à chaque objet réel sa propre table, stockez explicitement les relations et séparez l’état SEO d’une page des faits qu’elle affiche.
CREATE TABLE city (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
slug TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
region TEXT NOT NULL,
intro_text TEXT
);
CREATE TABLE space (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
external_id TEXT NOT NULL UNIQUE,
city_id BIGINT NOT NULL REFERENCES city (id),
name TEXT NOT NULL,
address TEXT NOT NULL,
day_pass_eur NUMERIC(6, 2),
has_meeting_rooms BOOLEAN NOT NULL DEFAULT false,
synced_at DATE NOT NULL
);
CREATE TABLE city_link (
from_city_id BIGINT NOT NULL REFERENCES city (id),
to_city_id BIGINT NOT NULL REFERENCES city (id),
PRIMARY KEY (from_city_id, to_city_id)
);
CREATE TABLE page (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
city_id BIGINT NOT NULL UNIQUE REFERENCES city (id),
path TEXT NOT NULL UNIQUE,
template TEXT NOT NULL,
title TEXT NOT NULL,
meta_description TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'draft'
CHECK (status IN ('draft', 'ready', 'published', 'pruned')),
wave SMALLINT,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
La table page travaille plus qu’il n’y paraît. status commande ce qui est publié ou retiré, wave pilote un déploiement par vagues, updated_at alimente le sitemap et template permettra de lire les résultats par groupe. Votre maillage interne sera généré à partir de city_link : une ville proche devient ainsi une décision que vous pouvez contrôler et corriger.
Vous pouvez construire exactement la même chose dans un tableur : un onglet par table, des identifiants stables, des recherches plutôt que des valeurs collées.
Décidez quelles lignes méritent une page
Votre base doit pouvoir refuser une page. La politique de Google sur l’abus de contenu à grande échelle vise les pages produites en masse et sans valeur, quelle que soit la méthode utilisée, et une ville avec un seul espace de coworking et un paragraphe généré correspond à ce profil. Les réponses des IA n’en feront rien non plus, puisque la page ne contient rien qui vaille d’être cité.
Écrivez la règle noir sur blanc et faites-en une requête. Le seuil ci-dessous est un exemple, fixez le vôtre selon ce dont une page utile a besoin.
SELECT c.slug, COUNT(s.id) AS spaces
FROM city AS c
LEFT JOIN space AS s ON s.city_id = c.id
GROUP BY c.slug
HAVING COUNT(s.id) < 3;
Ces villes restent en draft. Leurs espaces peuvent tout de même figurer sur une page régionale, où ils apportent quelque chose.
Choisissez l’outil que votre équipe maintiendra encore dans un an
| Outil | Quand il convient | Où il coince |
|---|---|---|
| Google Sheets | Démarrage rapide, formules lisibles par tous, connexion facile à Make ou Zapier | Ralentit sur les gros fichiers, faible sur les droits et la validation |
| Airtable | De vraies relations et des vues, sans code | Limites d’enregistrements et prix selon l’abonnement |
| PostgreSQL, MySQL | Contraintes, jointures, gros volumes, requêtes d’audit propres | Demande quelqu’un à l’aise avec SQL et l’hébergement |
| MongoDB, Firebase | Enregistrements de forme réellement variable, données déjà sur place | La donnée programmatique est souvent relationnelle, vous finirez par recoder les jointures |
Je commence dans Google Sheets et j’y reste tant que ça ne coince pas. Derrick comme Deux.io ont tourné sur Google Sheets et Webflow. Si vous publiez via un CMS, gardez le tableur ou la base comme source de vérité, traitez le CMS comme une simple destination, et vérifiez la limite d’éléments de votre abonnement avant de vous engager. Sur une stack statique comme Astro, la base est tout simplement lue au moment du build.
Le service des pages compte aussi : elles doivent provenir de fichiers statiques ou d’un cache CDN, pour qu’un crawl n’interroge pas votre base à chaque URL.
Automatisez les imports sans écraser votre travail
Les données arriveront d’API externes, de scrapers, d’un CRM ou de l’export d’un partenaire, acheminées par Python, Make ou Zapier. Trois habitudes vous protègent.
Identifiez chaque enregistrement importé par un identifiant externe stable et mettez-le à jour sur place. Vider puis recharger une table change vos identifiants et casse toutes les relations.
INSERT INTO space (external_id, city_id, name, address, day_pass_eur, synced_at)
VALUES ($1, $2, $3, $4, $5, CURRENT_DATE)
ON CONFLICT (external_id) DO UPDATE
SET name = EXCLUDED.name,
address = EXCLUDED.address,
day_pass_eur = EXCLUDED.day_pass_eur,
synced_at = EXCLUDED.synced_at;
Ne laissez jamais un import écrire dans une colonne éditée à la main. Les noms corrigés et les textes relus vont dans des champs que la synchronisation ne touche pas.
Calez la fréquence sur la vitesse à laquelle la donnée change vraiment. Les prix d’un coworking justifient une tâche hebdomadaire. Une synchro horaire n’ajoute que des occasions de panne, sauf si vos données bougent à l’heure.
Gardez le texte généré dans ses propres colonnes
Titres, meta descriptions et intertitres gagnent le plus souvent à être construits par formule à partir de vos champs, car ils restent exacts quand la donnée change. Là où vous utilisez l’IA, pour un paragraphe d’intro par exemple, stockez le résultat dans une colonne dédiée avec un statut de relecture, ne lui fournissez que des faits issus de vos tables, et passez un contrôle de duplication avant toute mise en ligne. La stack de Deux.io associait l’IA à un vérificateur de contenu IA et à un vérificateur de contenu dupliqué. Derrick a été construit sans aucune IA, preuve que c’est la donnée qui porte la page.
Auditez la donnée avant qu’elle ne devienne une page
Ces contrôles doivent tourner automatiquement, et tout échec doit empêcher la ligne de passer en ready :
- champs obligatoires vides ou contenant des valeurs fictives
- titres ou meta descriptions en double
- enregistrements dupliqués sous des noms légèrement différents
synced_atplus ancien que votre règle de fraîcheur- valeurs hors d’une plage plausible, comme un pass journée à 0 euro
À gros volume, envoyez ces résultats dans BigQuery et un tableau de bord Looker Studio : un import cassé se verra comme un pic, sans que personne ait à le repérer.
Reliez ensuite la donnée aux résultats. Comme chaque ligne de page connaît son template, sa vague et la quantité de données qu’elle contient, vous pouvez vérifier si les pages les mieux fournies performent réellement mieux. Je détaille cette jointure dans le chapitre sur le suivi des performances, avec Search Console et les données de crawl de Screaming Frog ou Ahrefs.
Votre prochaine étape : écrivez le tableau zone, champ et origine pour un template, puis appliquez votre règle de seuil à vos vraies données. Le nombre de lignes restantes donne la taille honnête de votre projet, et ce sont ces pages qu’il faut emmener vers la mise en production et l’indexation.