- Sequin 팀은 React SPA와 순수 Phoenix LiveView를 모두 써본 뒤, LiveSvelte로 LiveView와 Svelte를 결합하는 방식이 가장 생산적이라고 판단함
- LiveView는 서버 상태와 DOM 증분 업데이트로 SPA 없이 풍부한 UI를 만들 수 있지만, 클라이언트 상태가 필요한 순간 JS 모듈·훅·LiveView 상태가 섞이며 경계가 흐려짐
- 순수 LiveView의 LiveView, LiveComponent, Component는 수명주기·props 전달·통신 방식이 달라 리팩터링과 상태 배치가 React보다 번거로움
- LiveSvelte는 Elixir가 props를 내려보내고 Svelte가
live.pushEvent로 이벤트를 올리는 명확한 양방향 경계를 제공함 - 이 조합에서는 라우팅·비즈니스 로직·데이터 준비를 서버에 두고, UI 상태·애니메이션·조건 렌더링은 Svelte가 맡아 SPA의 복잡도를 줄일 수 있음
LiveView가 제공하는 개발 모델
- 전통적인 서버 렌더링 앱은 서버가 상태를 갖지 않고, 클라이언트 요청마다 페이지를 렌더링함
- SPA는 클라이언트가 페이지 구성을 맡고, 백엔드 API로 데이터를 읽고 쓰며, React의
useState같은 방식으로 클라이언트 상태를 관리함 - LiveView는 서버가 페이지 렌더링을 계속 담당하면서도 상태를 유지하고, 프런트엔드 액션을 처리한 뒤 DOM을 SPA처럼 증분 업데이트함
- SPA의 복잡성은 클라이언트 JS 앱이 사실상 하나의 프런트엔드 마이크로서비스처럼 동작하는 데서 커짐
- 프런트엔드 전용 요구사항 때문에 백엔드 라우트와 컨트롤러가 늘어남
- 한 요청이
onMount, API 호출, 응답 파싱, 라우터, 인증 플러그, 컨트롤러, 도메인 함수, 응답 준비 계층을 거칠 수 있음
- LiveView의 장점은 프런트엔드 마이크로서비스 없이도 풍부한 클라이언트 경험을 만들 수 있다는 점임
- 테이블 렌더링 함수 근처에서 데이터베이스를 조회할 수 있음
- 새 행이 들어오면 테이블에 push하고, LiveView가 클라이언트를 업데이트함
- 요청마다 전체 상태를 다시 만드는 기존 서버 렌더링 방식보다 상태ful 프런트엔드 프레임워크에 가까운 상호작용을 만들 수 있음
순수 LiveView에서 부딪힌 마찰
-
클라이언트 측 상태는 피할 수 없음
- LiveView는 서버와 사용자 사이의 왕복 지연을 피할 수 없음
- 애니메이션, 툴팁, DOM 표시·숨김, 폼 필드 비활성화 같은 작업은 클라이언트에서 처리해야 하는 경우가 생김
- 두 개의 의존적인 드롭다운이 있는 폼에서는 첫 번째 드롭다운 선택 직후 두 번째 드롭다운을 즉시 비활성화하고, 서버가 새 목록을 채운 뒤 다시 활성화해야 좋은 UX가 됨
- 이 동작을 LiveView만으로 구현하려면 여러 개념이 함께 필요함
- JS, 훅, LiveView 상태가 함께 쓰이면 DOM 상태 일부가 LiveView 바깥에 존재하게 되고, 일관된 패턴을 잡기 어려움
-
React와 다른 상태 경계
- React는 상태와 액션이라는 핵심 개념으로 대부분의 전환을 처리하며, DOM 상태와 컴포넌트 상태의 경계가 비교적 선명함
- 클라이언트 액션과 클라이언트 상태 사이에 지연이 없기 때문에 React는 상태 패러다임으로 액션과 전환을 다룰 수 있음
- LiveView의 상태는 서버 측에 있으므로, 클라이언트 액션과 서버 상태 사이의 지연을 고려해야 함
- 입력 필드 예시에서 React는 문자가 입력되기 전에 상태 경로를 거치고, 컴포넌트가 매 키 입력마다 다시 렌더링됨
- LiveView는 사용자가 먼저 입력 필드를 바꾸고, 짧은 시간이 지난 뒤 서버 상태가 이를 알게 되어 반응하는 모델에 가까움
- 지연이 거의 없으면 React처럼 보이지만, 지연이 커질수록 두 모델의 차이가 분명해짐
세 가지 컴포넌트가 만드는 복잡성
- LiveView에는 LiveViews, LiveComponents, Components라는 세 가지 컴포넌트 유형이 있음
- LiveViews와 LiveComponents는 React의 상태ful 컴포넌트에 가깝고, Components는 함수형 컴포넌트에 가까움
- LiveView는 항상 최상위 부모 컴포넌트가 되며, 그 아래에 LiveComponents와 Components가 렌더링됨
- React에서는
useState훅을 추가하거나 제거하는 방식으로 상태ful 컴포넌트와 함수형 컴포넌트 사이를 쉽게 오갈 수 있음- 둘 다 props를 같은 방식으로 받음
- 상태를 제외하면 기능 세트가 동일함
- DOM 이벤트 등록과 응답 방식도 같음
- LiveView에서는 세 컴포넌트 유형의 차이가 커서 LiveView를 LiveComponent로 리팩터링하기 번거로움
- LiveViews와 LiveComponents는 렌더링 및 props 전달 문법이 다름
- 수명주기가 다름
- 통신 옵션이 다르며, LiveViews에는
send, LiveComponents에는send_update를 사용함 - LiveComponents는 프로세스가 아니어서 LiveViews처럼 시스템의 다른 부분과 상호작용할 수 없음
LiveComponent의 제약
- LiveView는 하나의 Elixir 프로세스이므로
pid, 상태, inbox를 갖고 Elixir/OTP 시스템의 다른 프로세스처럼 동작할 수 있음 - LiveView에서는 pub/sub를 사용해 시스템 전역 변경을 구독할 수 있음
- LiveComponent는 자체 프로세스가 아니라 LiveView가 호출하는 모듈임
- 부모 LiveView 프로세스가 하위 컴포넌트의 상태를 보유하므로, LiveView가 자식 LiveComponents의 메시지 라우팅까지 처리해야 함
- LiveComponents가 독립적인 상태 관리와 액션 처리 능력을 가지려면 각각이 프로세스여야 하지만, 현재 모델은 그렇지 않음
- 실제 개발에서는 LiveComponent에 이벤트나 액션을 보내고 싶어도 좋은 방법이 없고,
send_update를 액션 전달에 쓸지 상태 패치에 쓸지 애매해짐- 상태 패치에 사용하면
update절에서 마운트와 업데이트를 구분하는 방식도 고민해야 함
- 상태 패치에 사용하면
LiveSvelte가 만드는 경계
- LiveSvelte는 LiveView에서 Svelte 컴포넌트를 렌더링할 수 있게 해줌
- LiveView의
render안에서 Elixir 데이터 구조를 프런트엔드용 plain map으로 인코딩한 뒤 Svelte 컴포넌트의 props로 전달할 수 있음 - Svelte 컴포넌트는
export let으로 props를 받고, LiveSvelte가 제공하는liveprop을 통해 LiveView와 통신함 - Svelte 쪽에서는
live.pushEvent로 서버에 이벤트를 보낼 수 있음- 예시에서는 Svelte의 reactive block이
form변경 시"form_updated"이벤트를 서버로 보냄 - LiveView는
handle_event("form_updated", ...)에서 프런트엔드 파라미터를 디코딩하고 changeset을 갱신함 - changeset 검증 오류는 다시
errorsprop을 통해 프런트엔드로 전달됨
- 예시에서는 Svelte의 reactive block이
- 데이터는 Elixir에서 props로 Svelte에 내려가고, Svelte에서는 웹소켓 이벤트로 LiveView에 올라감
- LiveView 프로세스는 언제든 props를 업데이트해 Svelte 컴포넌트를 다시 렌더링하게 만들 수 있음
LiveView와 Svelte 조합의 핵심 속성
- 이 조합의 핵심은 상태ful 백엔드 프로세스와 상태ful 프런트엔드 컴포넌트가 함께 동작한다는 점임
- 세 가지 속성이 조합을 뒷받침함
- 백엔드가 프런트엔드 컴포넌트의 props를 제어함
- 프런트엔드와 백엔드가 모두 상태를 가짐
- 양쪽이 서로 메시지를 시작할 수 있는 비공개 양방향 통신 채널을 가짐
- 첫 번째 속성은 LiveView의 렌더링 방식 덕분에 가능함
- 서버 재렌더링 결과가 클라이언트에 자동으로 push되고 적용됨
- 서버가 JS 부모 컴포넌트처럼 컴포넌트 props를 업데이트할 수 있음
- 두 번째 속성은 LiveView가 Elixir 프로세스라는 점에서 나옴
- Elixir 프로세스는 상태를 캡슐화하는 방식임
- 세 번째 속성은 LiveView가 제공하는 지속적인 웹소켓 연결로 가능함
SPA와의 차이
- 브라우저 라우팅이 모두 백엔드에서 이뤄져 구조가 단순해짐
- 일반 SPA에서는 브라우저 라우트와 API 라우트라는 두 종류의 라우트를 유지해야 함
- 백엔드는 상태를 갖고 현재 라우트와 작업 중인 리소스를 알고 있음
- 각 액션은 상태를 처음부터 다시 만드는 대신, 백엔드 자신의 상태 변경으로 처리될 수 있음
- 프런트엔드와 백엔드의 통신은 비공개이고 결합되어 있음
- 단일 컴포넌트를 지원하기 위한 RPC 호출로 서버의 공개 라우트를 늘리지 않음
- 클라이언트에서
pushEvent를 보면 해당 핸들러가 협업하는 Elixir 모듈 안에 있음을 알 수 있음
- 기능은 주로 두 파일에 나뉨
- 백엔드 모듈은 데이터베이스 조회 같은 백엔드 함수를 호출함
- 프런트엔드는 컴포넌트와 스타일을 가져옴
- 프런트엔드와 백엔드 왕복이 API 모듈, 라우터, 컨트롤러 스택을 지나지 않음
- 통신 절차도 줄어듦
- 백엔드는 props 업데이트만으로 프런트엔드 변경을 알릴 수 있음
- 프런트엔드는 만료 토큰, 타임아웃, 장애 처리용 핸들러 없이
pushEvent를 보낼 수 있음 - 웹소켓이 열려 있으면 서버가 동작 중이고, 닫혀 있으면 LiveView가 전역 “disconnected” 배너를 보여줌
책임 분리와 코드 구조
- 이 구조에서는 프런트엔드 마이크로서비스가 제거된 것처럼 동작함
- 비즈니스 로직은 백엔드에 둠
- 데이터 로딩 방식
- 어떤 데이터를 불러올지
- 정렬과 필터링 방식
- 검증기
- Svelte 프런트엔드 코드는 단순해짐
- 조건 렌더링을 위한
if/end블록 - 애니메이션
- 서버로 보내는 간단한
pushEvent함수
- 조건 렌더링을 위한
- 일반적인 SPA 프런트엔드에는 서버 데이터 처리, 표시용 데이터 준비, 서버 전송용 데이터 준비를 위한
map,reduce,filter로직이 많아질 수 있음 - LiveSvelte 앱에서는 이런 처리를 서버 쪽에서 수행할 수 있음
- LiveView가 Svelte 컴포넌트가 필요한 형태로 데이터를 준비함
- 복잡성이 서버 언어, 서버 데이터 구조, 서버 테스트 스위트 안에 머무름
- LiveView와 Svelte 컴포넌트는 느슨히 결합된 두 요소라기보다 한 기능의 두 절반에 가까움
- LiveView는 해당 Svelte 컴포넌트만 렌더링함
- 그 Svelte 컴포넌트는 해당 LiveView에 의해서만 렌더링됨
순수 LiveView 대비 달라지는 점
- LiveSvelte 방식은 프런트엔드의 상태와 상태 전환을 받아들임
- 프런트엔드와 백엔드 사이의 경계층이 명확해짐
- Svelte의 성숙한 컴포넌트 패러다임을 활용할 수 있음
- 순수 LiveView만으로는 접근하기 어려운 JavaScript 프레임워크 생태계도 사용할 수 있음
- Svelte는 애니메이션 primitives를 제공함
- 더 많은 책임을 프런트엔드로 옮기면서 어색한 중간 지대에 걸친 느낌이 줄어듦
Svelte를 선택한 이유와 LiveView의 위치
- LiveSvelte를 선택한 이유는 React에 비슷하게 완성도 있는 LiveView 라이브러리가 없었기 때문임
- Svelte는 기본 상태와 반응성에서 가볍고 빠른 프런트엔드 프레임워크로 평가됨
- LiveView가 상태 관리의 큰 부분을 맡기 때문에 Svelte 쪽 상태 관리는 단순하게 유지됨
- Svelte의 템플릿 기능도 React보다 선호됨
- 삼항 연산자 대신
if/else를 사용할 수 있음 - 조건부 속성 설정을 사용할 수 있음
- 삼항 연산자 대신
- Svelte 5의 runes는 Svelte를 더 배우기 쉽고 추론하기 쉽게 만들 것으로 기대됨
- LiveView는 프런트엔드 컴포넌트를 렌더링하고, 증분 업데이트를 수행하며, 상태ful 백엔드 프로세스를 유지하고, 웹소켓 API를 제공하는 backend-for-frontend로 가장 빛남