홈으로 돌아가기

Next.js와 1C Bitrix 통합: 비동기 리드 전송

Next.js 16과 1C Bitrix의 비동기 통합 실전 가이드. 백그라운드 리드 처리를 위한 after() 사용, retry 메커니즘 및 PostgreSQL 스키마 설정. 타임아웃 및 중복 문제 해결.

Next.js와 1C Bitrix: 큐와 워커 없이 리드 전송하는 방법
Advertisement 728x90

# Next.js에서 1C-Bitrix로의 비동기 리드 전송: 큐와 워커 없는 패턴

REST API를 통해 CRM으로 동기식 리드 제출은 치명적인 문제를 초래합니다: 사용자가 외부 요청 완료까지 응답을 기다려야 하고, 1C-Bitrix API의 불안정성으로 타임아웃과 오류가 발생합니다. 우리는 Next.js 16에서 큐와 백그라운드 프로세스를 없애는 솔루션을 제안합니다. 이 구현은 리드를 로컬 PostgreSQL 데이터베이스에 저장한 후 사용자에게 즉시 응답하고, HTTP 응답 전송 후 백그라운드 처리를 위한 메커니즘인 after()를 통해 Bitrix와의 통합을 처리합니다.

큐 없는 아키텍처: 세 층의 책임 분리

핵심 원칙은 작업을 동기식과 비동기식 부분으로 분리하는 것입니다. 폼을 받으면:

  • zod를 통한 데이터 유효성 검사
  • bitrix_id=NULL로 PostgreSQL에 저장
  • 즉시 200 OK 응답
  • after()를 통한 백그라운드 Bitrix 전송

이렇게 하면:

Google AdInline article slot
  • Bitrix REST API 안정성에 독립적
  • 사용자 인터페이스 차단 없음
  • SELECT * FROM leads WHERE bitrix_id IS NULL로 전달 실패 리드 수동 처리 가능

이 접근의 핵심은 Redis/BullMQ를 버리는 것입니다. 모든 작업이 Next.js API 라우트에 맞고, 응답 후 처리를 위해 내장 after()를 사용합니다. 이는 인프라 복잡성을 줄이고 VPS에서 systemd/nginx를 통한 배포를 간소화합니다.

Next.js 16의 after(): 기술적 구현

after() 메서드는 서버리스 환경에서 "fire-and-forget" 문제를 해결합니다. Promise.resolve().then()과 달리 응답 전송 후에도 작업 실행을 보장하며, 작업 완료까지 프로세스를 종료하지 않습니다. 폼 처리 예시:

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 });
}

주요 주의점:

Google AdInline article slot
  • PostgreSQL의 10분 윈도우를 통한 중복 방지 구현(Bitrix는 경고 없이 중복 허용)
  • 비동기 작업 특성을 반영해 bitrix_id를 nullable text로 저장
  • 통합 오류는 journalctl에 로그로 기록, 사용자 응답에 영향 없음

1C-Bitrix를 위한 안정적인 HTTP 클라이언트

불안정한 API와 안정적으로 작업하기 위한 핵심 매개변수:

  • MAX_ATTEMPTS = 2 — 신뢰성과 부하 균형
  • REQUEST_TIMEOUT_MS = 8000 — Bitrix p95 지연(400-700 ms) 초과
  • RETRY_DELAY_MS = 1500 — 지수적 복잡도 없는 선형 백오프

클라이언트 구현:

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);
    }
  }
}

세 가지 핵심 포인트:

Google AdInline article slot
  • response.text()를 통한 수동 파싱 — 빈 응답 오류 우회
  • HTTP 상태와 CRM 내부 오류 두 유형 처리
  • finally에서의 타이머 정리 보장 — 메모리 누수 방지

인증 기능과 데이터 스키마

통합을 위해 OAuth 대신 인바운드 웹훅 선택:

  • Marketplace와 승인 의존성 없음
  • BITRIX_WEBHOOK_URL 환경 변수 간단 관리
  • Bitrix 관리 패널에서 스코프 설정

URL 내 토큰 유출 위험은 로그와 코드에서 전체 URL이 아닌 API 메서드만 표시하는 규칙으로 최소화.

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()
);

bitrix_id를 text로 한 이유, integer가 아닌?

  • Bitrix가 JSON에서 ID를 문자열로 반환
  • 타입 변환 불필요
  • PostgreSQL UUID로 멱등성 보장

주요 포인트

  • 단일 진실 원천 — 로컬 DB: Bitrix 연락 전에 리드 저장으로 CRM 실패 시에도 데이터 보장
  • 응답 후 처리: after()가 큐를 대체해 인프라 복잡성 제거
  • 오류 처리: 타임아웃 재시도와 journalctl 로깅으로 관찰 가능성 보장
  • DB 수준 중복 방지: 전화번호 기준 10분 윈도우로 CRM과 무관하게 중복 방지
  • 안전한 인증: 웹훅은 엄격한 URL 로깅 제어 필요

이 패턴은 분당 50건 이하 요청 프로젝트에 적합합니다. 고부하 시스템에서는 SELECT COUNT(*) FROM leads WHERE bitrix_id IS NULL로 모니터링 추가와 cron을 통한 자동 재시도 고려. 현재 구현에서는 자동 재시도가 임시 실패 시 리드 중복 위험을 줄이기 위해 수동 오류 처리 선호.

— Editorial Team

Advertisement 728x90

다음 읽기