ENFRKOantony langlois
작업

XiLearn

방문웹사이트Next.jsNotion APIStripeMoodle

라오스를 위한 3개 국어 이러닝 플랫폼 · Notion을 유일한 데이터베이스로 쓰는 Next.js 스토어프론트가 강좌를 판매합니다 · 자체 호스팅한 Moodle이 강좌를 가르칩니다 · en / fr / lo 로 Stripe 결제

아키텍처

Next.js client
프런트엔드 · 스토어프론트
Storefront UI
src/app/[locale] · app router
카탈로그, 강좌 페이지, 결제 · Stripe PaymentElement 렌더링
SaaS
외부 · 결제
Stripe
Payment Intents · 브라우저에서 카드 확인
Node
코어 · Next.js 서버
App Router server
src/app · SSR + API routes
매 렌더마다 Notion 읽기 · /api/create-payment-intent · /api/addStudent
next-intl locale middleware서버 데이터를 클라이언트 컨텍스트로멱등 가입 쓰기hreflang + sitemap SEO비밀 키는 서버 사이드 전용
php · apache
서비스 · LMS
Moodle LMS
xilearn-moodle · Docker
자체 호스팅 · 분당 cron 하트비트 내장
SaaS
외부 · CMS + 백오피스
Notion databases
강좌 · 카테고리 · 학생 대기열 (Need to be added)
localhost:8081
도구 · DB 관리
phpMyAdmin
개발 전용 Moodle 테이블 검사
Docker volume
데이터베이스 · LMS 데이터
MariaDB
mdl_* 테이블 · moodledata 볼륨의 파일

방문자가 /lo 의 Storefront UI 에 도착하면 App Router 서버Notion에서 3개 국어 카탈로그를 가져와 서버 사이드로 렌더링하여 응답합니다. 결제 시 서버는 /api/create-payment-intent 를 통해 PaymentIntent를 생성하고, 브라우저는 Stripe의 Payment Element로 카드를 확인하며, /api/addStudent 는 이메일 + 강좌 기준으로 중복 제거하여 Need to be added 상태로 학습자를 Notion 학생 대기열에 기록합니다. 운영팀이 그 대기열을 처리하여 학습자를 Moodle에 수동으로 등록하는데, Moodle은 자체 Docker 스택으로 실행되며 모든 것을 MariaDB에 저장합니다 · phpMyAdmin이 개발 중 검사를 위해 함께 따라옵니다.

배포: 스토어프론트 → Next.js · xilearn.com · LMS 스택 → Docker Compose · Moodle + MariaDB + phpMyAdmin

성과

  • 3개 로케일 · en / fr / lo · 라오 문자는 전용 폰트를 씁니다
  • 관리 UI 0개 · Notion이 CMS, 백오피스, 등록 대기열입니다
  • 2개 시스템 · 스토어프론트와 LMS가 독립적으로 배포됩니다
  • 1개 명령 · docker compose로 전체 LMS 스택 기동

보여준 역량: 헤드리스 CMS 데이터 모델링 · Stripe Payment Intents 생명주기 · 로케일 접두 SEO 라우팅 · PHP 모놀리스를 Docker에 패키징 · 멱등 쓰기 가드

문제와 해결책

모든 선택은 하나의 제약에 답합니다: 3개 국어 강좌를 판매하는 작은 팀은 맞춤형 백엔드 시스템을 구축하거나 관리할 여유가 없습니다.

▩ LMS는 사서 쓰고, 스토어프론트는 직접 만든다

문제: 진짜 강좌 플랫폼에는 레슨, 퀴즈, 진도 추적, 수료증이 필요합니다. 그것을 혼자 만들면 몇 년이 걸리지만, 기성품 Moodle은 낡아 보이고 마케팅, SEO, 카드 결제를 할 수 없습니다.

제품을 둘로 나눕니다: Next.js 스토어프론트가 탐색, 언어, 결제를 담당하고, 자체 호스팅한 Moodle이 모든 교육을 담당합니다. 둘은 등록 시점에서만 만납니다.

  • 맞춤형 LMS 구축은 배제: 퀴즈와 진도 추적은 이미 해결된 문제이지 차별화 요소가 아닙니다
  • Moodle을 테마로 마케팅 사이트로 만드는 것도 배제: 모든 픽셀을 두고 PHP 모놀리스와 싸우는 일입니다
  • 감수한 절충: 두 개의 배포와 다리를 놓아야 하는 이음새

▦ CMS, 데이터베이스, 백오피스로서의 Notion

문제: 강좌 문구는 영어, 프랑스어, 라오어로 존재하며 비개발자가 편집합니다. 맞춤형 관리 패널이나 헤드리스 CMS 구독은 첫 강좌가 팔리기도 전에 하나의 프로젝트가 됩니다.

Notion 데이터베이스가 스토어프론트의 유일한 데이터 저장소입니다. 강좌, 카테고리, 학생 가입은 팀이 이미 매일 편집하는 테이블에 존재하며, 서버는 매 렌더마다 공식 API를 통해 이를 읽습니다.

  • Contentful 방식의 SaaS를 이김: 편집자를 위한 새 도구 제로, 추가 비용 제로
  • 한 행이 세 언어를 나란히 담아 번역이 서로 어긋나지 않습니다
  • 약한 쿼리 성능과 API 지연은 감수: 카탈로그 크기가 둘 다 무의미하게 만듭니다

▧ Moodle API 브리지 대신 사람이 처리하는 등록 대기열

문제: 결제 후 학습자에게는 Moodle 계정과 강좌 등록이 필요합니다. Moodle 웹서비스 자동화는 설정이 취약하고, 초기에는 모든 결제가 사람의 눈길을 받을 가치가 있습니다.

결제는 학습자를 Need to be added 상태로 Notion 테이블에 기록하고, 운영팀이 대기열을 처리하여 수동으로 등록합니다. 이메일 + 강좌 + 상태에 대한 중복 제거 필터가 재시도로 인한 중복 행 생성을 막습니다.

  • Moodle 웹서비스를 통한 자동 프로비저닝은 배제: 현재 물량에서 사람이 몇 초면 하는 단계에 며칠의 통합 작업
  • 수동 검토는 결제 검증도 겸합니다
  • 감수한 절충: 접근이 즉각적이지 않으며, 이는 코호트형 강좌가 용인합니다

▥ 호스팅형 리디렉션이 아닌 임베디드 Stripe 결제

문제: 구매자를 외부 결제 페이지로 보내는 것은, 온라인 카드 결제 자체가 이미 큰 도약인 시장을 대상으로 하는 사이트에서 신뢰를 깨뜨립니다.

서버는 결제마다 PaymentIntent를 생성하고 브라우저는 Stripe의 Payment Element를 인라인으로 렌더링하며, /payment-success 로의 리디렉션으로 확인합니다. 비밀 키는 결코 서버를 떠나지 않습니다.

  • Stripe 호스팅형 Checkout은 배제: 가장 민감한 순간의 브랜드에 맞지 않는 리디렉션
  • QR 코드 결제 경로를 프로토타이핑했다가, 현지 제공업체가 유지관리 가치를 가질 때까지 보류했습니다
  • 자동 결제 수단은 Stripe가 각 구매자에게 무엇을 제안할지 결정하게 합니다

▨ 검색 엔진이 색인할 수 있는 3개 국어 라우팅

문제: 라오 문자는 라틴 폰트에서 잘 렌더링되지 않고, 클라이언트 사이드 번역은 라오어와 프랑스어 카탈로그를 검색 엔진으로부터 숨깁니다.

next-intl 미들웨어가 hreflang 대체와 함께 로케일 접두 URL (/en, /fr, /lo)을 제공하고, 레이아웃은 로케일이 lo 일 때 Noto Sans Lao로 전환합니다. UI 문자열은 JSON에, 콘텐츠 번역은 Notion 컬럼에 존재합니다.

  • 클라이언트 사이드 i18n 라이브러리는 배제: 언어별로 색인 가능한 URL이 없습니다
  • 로케일별 폰트 로딩은 모두에게 모든 폰트를 보내지 않고도 라오어 텍스트를 읽기 좋게 유지합니다
  • 감수한 비용: 모든 콘텐츠 필드가 CMS에 세 번 존재합니다

▣ 맞춤형 Docker 이미지 안에 소스로 빌드한 Moodle

문제: Moodle에는 정확한 PHP 확장, 쓰기 가능한 데이터 디렉터리, cron 하트비트가 필요합니다. 손으로 설치한 서버는 썩어가고 망가졌을 때 재구축할 수 없습니다.

맞춤형 php-apache Dockerfile이 모든 확장, opcache, 분당 cron을 구워 넣습니다 ; config.php 와 Moodle 소스는 볼륨으로 마운트되고 전체 스택은 하나의 compose 명령으로 시작됩니다.

  • 미리 빌드된 Bitnami 이미지는 배제: 불투명한 레이어, 정확한 Moodle 릴리스를 고정하기 어려움
  • 소스 체크아웃을 안정 브랜치에 고정하여 업그레이드가 의도적으로 이루어집니다
  • 이미지 소유를 감수: 그 대가로 개발과 운영이 비트 단위로 동일합니다