Transfert asynchrone de prospects de Next.js vers 1C-Bitrix : Un pattern sans file d'attente ni worker
La soumission synchrone de prospects au CRM via l'API REST provoque des problèmes critiques : l'utilisateur attend une réponse jusqu'à ce que la requête externe se termine, et l'instabilité de l'API 1C-Bitrix entraîne des timeouts et des erreurs. Nous proposons une solution sur Next.js 16 qui élimine les files d'attente et les processus en arrière-plan. L'implémentation enregistre le prospect dans une base de données PostgreSQL locale, répond instantanément à l'utilisateur et gère l'intégration avec Bitrix via after() — un mécanisme de traitement en arrière-plan après l'envoi de la réponse HTTP.
Architecture sans file d'attente : Trois couches de responsabilité
Principe clé — séparer les opérations en parties synchrones et asynchrones. Lors de la réception du formulaire :
- Validation des données via zod
- Enregistrement en PostgreSQL avec bitrix_id=NULL
- Réponse immédiate 200 OK
- Envoi en arrière-plan vers Bitrix via after()
Cela garantit :
- L'indépendance de la stabilité de l'API REST Bitrix
- Pas de blocage de l'interface utilisateur
- Possibilité de traiter manuellement les prospects non livrés via SELECT * FROM leads WHERE bitrix_id IS NULL
La caractéristique clé de cette approche est l'abandon de Redis/BullMQ. Toutes les opérations s'intègrent dans une route API Next.js, en utilisant after() intégré pour le traitement post-réponse. Cela réduit la complexité de l'infrastructure et simplifie le déploiement sur VPS via systemd/nginx.
after() dans Next.js 16 : Implémentation technique
La méthode after() résout le problème du « fire-and-forget » dans les environnements serverless. Contrairement à Promise.resolve().then(), elle garantit l'exécution de la tâche même après l'envoi de la réponse, sans terminer le processus avant l'achèvement de l'opération. Exemple de traitement de formulaire :
export async function POST(request: Request) {
// ... validatsiya and antidubl
const [lead] = await pgQuery(...);
after(async () => {
try {
const bitrixId = await sendToBitrix24(payload);
await pgQuery(
`UPDATE leads SET bitrix_id = $1 WHERE id = $2`,
[bitrixId, lead.id]
);
} catch (error) {
console.error("[Leads API] Error otpravki in Bitriks:", error);
}
});
return Response.json({ ok: true, id: lead.id });
}
Nuances clés :
- Anti-duplication implémentée via une fenêtre de 10 minutes en PostgreSQL, pas via Bitrix (le CRM accepte les doublons sans avertissement)
- bitrix_id stocké comme text nullable — reflète la nature asynchrone de l'opération
- Les erreurs d'intégration sont journalisées dans journalctl, sans affecter la réponse utilisateur
Client HTTP fiable pour 1C-Bitrix
Paramètres critiques pour un travail stable avec une API instable :
- MAX_ATTEMPTS = 2 — équilibre entre fiabilité et charge
- REQUEST_TIMEOUT_MS = 8000 — au-dessus de la latence p95 de Bitrix (400-700 ms)
- RETRY_DELAY_MS = 1500 — backoff linéaire sans complexité exponentielle
L'implémentation du client inclut :
async function bitrixRequest(method: string, payload: Record<string, unknown>) {
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
try {
const response = await fetch(url, {
signal: controller.signal,
// ...
});
const text = await response.text();
const json = text ? JSON.parse(text) : {};
if (!response.ok || json.error) {
throw new Error(json.error_description || `HTTP ${response.status}`);
}
return json;
} catch (error) {
if (attempt >= MAX_ATTEMPTS) throw error;
await new Promise(r => setTimeout(r, RETRY_DELAY_MS));
} finally {
clearTimeout(timer);
}
}
}
Trois points clés :
- Parsing manuel via response.text() au lieu de response.json() — contourne les erreurs Bitrix avec réponses vides
- Gestion de deux types d'erreurs : statuts HTTP et erreurs internes du CRM
- Nettoyage garanti du timer dans finally — prévient les fuites mémoire
Fonctionnalités d'authentification et schéma de données
Pour l'intégration, webhook entrant choisi plutôt qu'OAuth :
- Pas de dépendance au Marketplace et aux approbations
- Gestion simple de la variable d'environnement BITRIX_WEBHOOK_URL
- Scopes configurés dans le panneau d'administration Bitrix
Risque de fuite de token dans l'URL minimisé par la règle : les logs et le code n'affichent que la méthode API, pas l'URL complète.
Schéma de la table leads :
CREATE TABLE leads (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
phone text NOT NULL,
source text,
bitrix_id text,
created_at timestamptz NOT NULL DEFAULT now()
);
Pourquoi bitrix_id text, pas integer ?
- Bitrix retourne l'ID comme string en JSON
- Pas besoin de conversion de type
- UUID en PostgreSQL assure l'idempotence
Points clés
- Source unique de vérité — DB locale : prospect enregistré avant contact avec Bitrix, assurant les données même en cas d'échec du CRM
- Traitement post-réponse : after() remplace les files d'attente, éliminant la complexité infrastructurelle
- Gestion des erreurs : tentatives avec timeouts et journalisation journalctl assurent l'observabilité
- Anti-duplication au niveau DB : fenêtre de 10 minutes par téléphone prévient les doublons indépendamment du CRM
- Authentification sécurisée : webhook nécessite un contrôle strict des logs d'URL
Ce pattern convient aux projets avec jusqu'à 50 requêtes par minute. Pour les systèmes à forte charge, ajoutez une surveillance via SELECT COUNT(*) FROM leads WHERE bitrix_id IS NULL et des tentatives automatiques via cron. Dans l'implémentation actuelle, la gestion manuelle des erreurs est préférable aux tentatives automatiques — cela réduit le risque de duplication de prospects lors de pannes temporaires.
— Editorial Team
Aucun commentaire pour le moment.