라오스를 위한 3개 국어 이러닝 플랫폼 · Notion을 유일한 데이터베이스로 쓰는 Next.js 스토어프론트가 강좌를 판매합니다 · 자체 호스팅한 Moodle이 강좌를 가르칩니다 · en / fr / lo 로 Stripe 결제
아키텍처
방문자가 /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 릴리스를 고정하기 어려움
- 소스 체크아웃을 안정 브랜치에 고정하여 업그레이드가 의도적으로 이루어집니다
- 이미지 소유를 감수: 그 대가로 개발과 운영이 비트 단위로 동일합니다