URL: https://www.youtube.com/watch?v=HfRQjix9tU4 발행일: 2026-09-27 정리일: 2026-09-29 채널: Dreams of Code
📌 핵심 질문 / Rust 웹 개발을 다시 보게 만든 핵심 논점
Rust 웹 개발이 좋아진 진짜 이유는 기존에 기대했던 WebAssembly(WASM) 중심 생태계의 성숙이 아니라, 서버 중심 설계와 낮은 보일러플레이트를 결합한 새로운 프레임워크 Topcoat의 등장이다. ==Topcoat는 Rust 표현식을 JavaScript로 컴파일하고, 서버 호출·서버 재렌더링·요청 단위 메모이제이션을 하나의 모델로 묶어 무거운 프런트엔드 프레임워크 없이도 상호작용형 웹 애플리케이션을 만든다.==
- 기존 Rust 웹 프레임워크의 클라이언트 상호작용은 주로 WebAssembly에 의존했지만, Topcoat는 단순한 런타임 표현식을 JavaScript로 컴파일한다.
- 복잡한 작업은
procedure로 서버 코드를 호출하고,shard로 필요한 HTML 조각만 서버에서 다시 렌더링한다. request context,app context,memoise같은 기능이 데이터베이스 연결·신호(signal)·요청 단위 캐시를 일관되게 연결한다.
Topcoat의 매력은 Rust의 타입 시스템을 포기하지 않으면서도 브라우저 API와 JavaScript 라이브러리를 다루는 마찰을 줄이고, 상호작용이 필요한 부분만 서버와 클라이언트 사이에서 정교하게 분리하는 데 있다. 다만 프로젝트가 아직 실험 단계이므로 현재는 프로덕션 애플리케이션의 기본 선택으로 보기 어렵다.
1. 기존 판단의 수정과 Topcoat의 등장
1.1. Rust 웹 생태계가 좋아진 이유를 잘못 짚었던 배경
-
초기 판단의 출발점
- 올해 초 Rust 웹 개발을 다룬 이전 글에서 Rust on the web이 점점 좋아지고 있다고 평가했다.
- 당시에는 그 이유를 여러 인기 웹 프레임워크와 WebAssembly를 포함한 기존 생태계의 발전에서 찾고, 관심 있는 프레임워크를 개괄했다.
-
판단을 뒤집은 변화
- 불과 몇 달 뒤 Topcoat라는 새 웹 프레임워크를 사용하면서 웹 애플리케이션을 Rust로 만드는 방식 자체가 달라졌다.
- Rust 웹이 좋아진다는 결론은 맞았지만, 그 원인은 처음 생각한 프레임워크들이 아니라 Topcoat가 제공하는 서버 중심 패러다임에 있었다.
1.2. 겉으로는 익숙한 현대적 프레임워크
-
표면적인 기능 구성
- Topcoat는 서버 사이드 렌더링(server-side rendering)을 제공한다.
- API 엔드포인트를 만들 수 있고, shadcn에서 영감을 받은 컴포넌트 라이브러리를 제공한다.
- Tailwind CSS를 기본 지원하고 클라이언트 사이드 상호작용도 처리한다.
-
기존 Rust 프레임워크와 닮은 프로그래밍 모델
- 컴포넌트 함수(component function)라는 개념을 사용한다.
view매크로로 HTML을 정의한다.- 클라이언트 상태를 관리하고 반응시키는 signal 기반 추상화를 사용한다.
- 따라서 기능 목록과 코드 형태만 보면 다른 Rust 웹 프레임워크와 크게 다르지 않아 보인다.
2. WebAssembly 대신 Rust를 JavaScript로 컴파일하는 클라이언트 모델
2.1. WebAssembly가 주는 능력과 마찰
-
기존 접근의 장점
- 대부분의 Rust 웹 프레임워크는 클라이언트 사이드 상호작용을 WebAssembly로 구현한다.
- WebAssembly는 매우 강력하며 Rust 코드를 브라우저에서 실행할 수 있다는 장점이 있다.
-
실제 애플리케이션에서 생기는 문제
- 브라우저 API와 JavaScript 라이브러리를 함께 써야 하는 비사소한(non-trivial) 웹 애플리케이션에서는 WebAssembly와 외부 세계를 연결하는 작업이 번거롭다.
- JavaScript 생태계의 라이브러리나 브라우저 기능을 사용하려면 별도 바인딩과 상호운용 계층을 거쳐야 하므로, 단순한 UI 상호작용 이상의 작업에서 마찰이 커진다.
2.2. 서버 렌더링과 JavaScript 컴파일의 분리
-
초기 렌더링은 서버가 담당한다
- 서버는 모든 마크업을 렌더링한다.
- 초기 렌더링에 필요한 런타임 표현식(runtime expression)도 서버에서 평가한다.
-
브라우저에서 같은 표현식을 실행하는 방식
- 클라이언트에서 같은 표현식을 실행하기 위해 Topcoat는 Rust 표현식을 JavaScript로 컴파일한다.
- 이 방식은 클라이언트 런타임 모델을 단순하게 만들고 WebAssembly가 추가하는 상호운용 마찰을 우회한다.
- 브라우저 API나 JavaScript 라이브러리에 접근해야 할 때는
raw매크로를 사용해 표현식 안에 원시 JavaScript를 직접 작성할 수 있다. raw매크로는 Rust와 브라우저·JavaScript 생태계 사이의 실용적인 탈출구(escape hatch) 역할을 한다.
-
컴파일 범위의 타협
- Rust를 효과적으로 JavaScript로 컴파일하려면 런타임 표현식에서 지원하는 Rust 문법의 부분집합이 제한된다.
- 이 제한만 보면 복잡한 상호작용을 처리할 수 없는 것처럼 보이지만, Topcoat의 설계 의도는 런타임 표현식을 단순한 클라이언트 상호작용에 집중시키는 것이다.
- 복잡한 동작은 클라이언트에서 Rust 코드를 억지로 실행하는 대신 서버 호출과 서버 렌더링이라는 핵심 패러다임으로 옮긴다.
3. procedure: 클라이언트에서 서버 함수를 호출하는 연결 계층
3.1. 서버 중심 개발을 자연스럽게 만드는 호출 방식
-
서버 중심 선호와 Topcoat의 적합성
- 서버 사이드 솔루션을 선호하는 개발 흐름은 초기 강의 웹사이트를 Goth 스택으로 만든 경험과 연결된다.
- Topcoat는 서버와 클라이언트를 연결하는 방식을 별도 보일러플레이트 없이 제공하므로 이런 개발 성향에 잘 맞는다.
-
Procedure 정의와 호출
- 서버에서 실행할 코드를
async함수로 정의한다. - 함수에
procedure어트리뷰트를 붙이면 런타임 표현식에서 일반적인 Rust 비동기 함수처럼 호출할 수 있다. - Topcoat는 procedure를 바탕으로 HTTP 엔드포인트와 클라이언트 요청 코드를 자동 생성한다.
- 클라이언트 코드는 데이터베이스 쓰기처럼 서버에서 실행하는 편이 적합한 작업을 자연스럽게 요청할 수 있다.
- 서버에서 실행할 코드를
3.2. Neon Postgres를 이용한 데이터 저장 사례
-
데이터베이스 연결 준비
- procedure가 입력값을 데이터베이스에 저장하려면 먼저 데이터베이스 인스턴스가 필요하다.
- Neon 프로젝트와 필요한 스키마를 미리 구성하고 Neon CLI로 데이터의 새 브랜치를 체크아웃한다.
- Neon CLI는 새 브랜치에 대한 연결 URL을
.env파일에 자동으로 추가한다.
-
데이터베이스 브랜칭의 용도
- 데이터 브랜칭은 데모에서 빠르게 독립된 데이터를 마련하는 데 유용하다.
- 리뷰 앱(review app), 개발 환경, 프로덕션 데이터를 원본 프로덕션에 영향을 주지 않고 디버깅하는 상황에도 활용할 수 있다.
- Neon은 데이터베이스 브랜칭 외에도 자동 백업, 특정 시점 복구(point-in-time recovery), 데이터 익명화(data anonymization)를 제공한다.
- 이러한 기능은 프로덕션 애플리케이션 운영에 필수적이며, 2년 동안 Neon을 사용해 온 주요 이유로 제시된다.
- 소개된 기능은 Neon 무료 플랜에서도 사용할 수 있다.
-
Procedure에 DB 연결 전달
- Neon Postgres 연결을 만든 뒤 공유할 리소스 값을 라우터의
app context에 등록한다. - 이 방식은 Axum의
with_state로 값을 등록하는 방식과 유사하다. - Axum과 Topcoat가 같은 GitHub 조직에 속해 있다는 점이 이런 설계 유사성의 배경이며, 두 프로젝트를 모두 선호하는 이유이기도 하다.
- Neon Postgres 연결을 만든 뒤 공유할 리소스 값을 라우터의
3.3. app context와 request context의 역할
-
App context의 생명주기
app context는 단일 요청보다 오래 살아야 하는 값을 저장한다.- 데이터베이스 커넥션 풀처럼 여러 서버 사이드 핸들러가 읽어야 하는 값이 대표적인 사례다.
- procedure 같은 서버 핸들러는 request context를 통해 app context에 등록된 값을 읽는다.
-
Request context 주입
- 서버 핸들러 함수의 인자에 request context를 나타내는 파라미터를 선언하면 Topcoat가 자동으로 해당 값을 전달한다.
- 이 파라미터는 서버 핸들러가 요청 정보·신호·공유 리소스에 접근하는 공통 진입점이다.
-
Request context의 세 가지 용도
- HTTP 요청의 헤더, 경로, HTTP 메서드 같은 하위 요청 정보를 얻는다.
- request context를 첫 번째 인자로 받고 클로저를 두 번째 인자로 받는 signal을 만들어 서버에서 읽을 수 있는 반응형 값을 만든다.
- request context를 특정 함수에 전달해 app context에 등록된 값을 읽는다.
-
Rust 타입 시스템을 이용한 값 조회
- app context는 값을 키 문자열로 찾기보다 Rust의 타입 시스템을 사용해 값을 구분한다.
- 데이터베이스 연결을 꺼낼 때 바인딩하는 변수의 타입을 명시하면 Topcoat가 해당 값을 찾아준다.
- 연결을 얻은 뒤 procedure의 입력값을 저장하는 SQL 쿼리를 작성하면 서버 측 데이터 영속화가 완성된다.
-
다른 서버 핸들러로 확장
- request context는 procedure 외에도 page, layout, route, component 핸들러에서 사용할 수 있다.
- 클라이언트 중심 프레임워크에 익숙한 관점에서는 component가 서버 리소스에 접근하는 방식이 다소 이례적으로 보인다.
4. shard: 서버에서 HTML 일부만 다시 렌더링하는 반응성
4.1. 검색 결과가 서버에서 갱신되는 예시
-
사용자 경험
- 검색 폼에 쿼리를 입력하면 결과 HTML이 자동으로 갱신된다.
- 일반적인 프런트엔드 프레임워크에서는 이런 동작을 클라이언트 사이드 렌더링으로 처리한다.
- Topcoat에서는 이 작업을 전부 서버에서 처리하며, 방식은 HTMX를 사용해 본 사람에게 익숙할 수 있다.
-
Shard의 기본 정의
- shard는 원하는 인자를 받는
async함수로 정의한다. shard어트리뷰트를 붙이고 HTML view를 반환한다.- procedure처럼 일반 표현식 안에서 함수를 직접 호출하는 대신, 각 인자를 독립된 표현식으로 감싼 형태로 view 안에 삽입한다.
- shard는 원하는 인자를 받는
4.2. Signal 변화와 서버 렌더링
-
입력 표현식에 반응하는 재렌더링
- shard 인자에 연결된 signal 값이 바뀌면 Topcoat가 변경을 감지한다.
- Topcoat는 shard 함수의 HTTP 엔드포인트에 요청을 보내고, 서버에서 HTML을 렌더링한다.
- 새로 렌더링한 결과를 기존 DOM에 병합하므로 전체 페이지를 다시 그리지 않고 검색 결과 조각만 교체할 수 있다.
-
서버가 직접 읽는 Signal
- shard 내부에서 정의한 signal을 런타임 표현식 바깥에서 읽으면 서버가 그 값을 읽는 구조가 된다.
- 그 signal 값이 변경될 때도 서버가 shard를 다시 렌더링한다.
- 이 기능은 shard에만 한정되지 않고 component, layout, page 같은 다른 서버 핸들러에도 적용된다.
-
Page 핸들러 사례
- page 핸들러 안에
querysignal을 정의하고 바로 다음 줄에서 서버가 그 값을 읽을 수 있다. query가 바뀌면 page가 서버에서 다시 렌더링되고, 검색 결과가 갱신된다.
- page 핸들러 안에
4.3. Page 재렌더링과 Shard 격리의 차이
-
전체 페이지를 갱신하는 서버 반응성
- 일반 서버 핸들러가 서버에서 읽은 signal에 반응하면 전체 페이지가 서버에서 다시 렌더링된다.
- 새 페이지 결과가 클라이언트에 병합되므로 구현은 단순하지만 갱신 범위가 넓다.
-
Shard의 최적화 역할
- shard는 자체 HTTP 엔드포인트를 가지므로 변경된 HTML 조각만 독립적으로 다시 렌더링한다.
- 따라서 shard는 새로운 반응성 모델이라기보다 서버 왕복 범위를 좁히는 최적화 단위다.
- 전체 page와 shard 모두 서버 왕복이 필요하므로 클라이언트에서 즉시 처리하는 방식보다 지연 시간이 발생한다.
-
동시 요청 처리
- 클라이언트 상태가 서버 응답보다 빠르게 바뀌면 여러 요청이 동시에 진행될 수 있다.
- Topcoat는 한 틱 동안 발생한 여러 signal 변화를 합치는(coalescing) 방식으로 요청 수를 줄인다.
- 이미 진행 중인 이전 요청은 중단(abort)하고 최신 인자에 해당하는 요청만 승자로 남긴다.
- 이 동작은 검색어를 빠르게 입력할 때 오래된 결과가 최신 결과를 덮어쓰는 문제를 막는다.
5. 서버 우선 철학으로 무거운 프런트엔드 의존성 줄이기
5.1. Procedure·Server signal·Shard의 조합
-
상호작용 애플리케이션 구성
- procedure는 클라이언트에서 서버 작업을 호출한다.
- server-read signal은 서버 핸들러가 상태 변화를 감지하게 한다.
- shard는 그 변화에 따라 페이지 전체가 아닌 필요한 HTML만 다시 전송한다.
-
서버 우선의 결과
- 세 기능을 조합하면 무거운 프런트엔드 프레임워크 없이도 인터랙티브 웹 애플리케이션을 만들 수 있다.
- 클라이언트에서는 간단한 표현식과 신호만 관리하고, 복잡한 로직·데이터베이스 작업·HTML 생성은 서버에 남긴다.
- Topcoat의 장점은 개별 기능의 화려함보다 서버와 클라이언트 사이의 책임 분리가 일관된다는 데 있다.
5.2. memoise: 요청 단위 데이터베이스 중복 제거
-
중복 쿼리 문제
get_user_by_id라는 함수가 사용자 ID로 데이터베이스를 조회한다.- 하나의 procedure가 이 함수를 직접 한 번 호출하고,
check함수 내부에서 간접적으로 한 번 더 호출한다. - procedure를 실행하면 동일한 요청 안에서 같은 데이터베이스 쿼리가 두 번 발생한다.
- 동작 자체는 예상 가능하지만 같은 요청에서 같은 결과를 재사용하지 못하므로 성능 면에서는 바람직하지 않다.
-
Memoise 어트리뷰트의 동작
get_user_by_id에memoise어트리뷰트를 한 줄 추가하면 해당 요청의 데이터베이스 조회가 한 번으로 줄어든다.- request context를 파라미터로 받는 함수라면
memoise를 적용할 수 있다. - Topcoat는 함수의 인자를 키로 삼아 요청이 끝날 때까지 결과를 캐시한다.
- 동기 함수와 비동기 함수 모두 지원한다.
- 동일한 작업을 동시에 요청하는 single-flight 처리도 기본 제공한다.
- 한 줄의 어트리뷰트로 백엔드의 흔한 중복 조회 문제를 해결한다는 점이 Topcoat의 간결함을 보여준다.
5.3. 일관된 개발 경험을 만드는 주변 기능
-
모듈과 자산 처리
- 모듈 기반 라우팅(module-based routing)이 파일과 기능의 경계를 정리한다.
- 자산 번들링(asset bundling)이 브라우저에 전달할 정적 자원을 묶는다.
- Topcoat CLI가 프로젝트 생성과 개발 흐름을 단순하게 만든다.
-
Live 기능
- 최근 추가된
live기능은 서버가 UI 업데이트를 클라이언트로 직접 push할 수 있게 한다. - 이 기능은 서버가 상태 변화를 감지하고 화면 업데이트를 밀어 넣는 Topcoat의 서버 우선 철학을 확장한다.
- 최근 추가된
6. 현재의 한계와 도입 판단
6.1. 실험 단계라는 사실
-
Breaking change 가능성
- Topcoat는 여전히 실험적인 상태다.
- 프로젝트가 발전하는 동안 API와 설계에 호환성이 깨지는 변경(breaking change)이 발생할 수 있다.
-
프로덕션 도입 위험
- 따라서 지금 당장 프로덕션 웹 애플리케이션의 기반으로 Topcoat를 선택하는 것은 권장되지 않는다.
- 문제가 발생할 때 직접 원인을 추적하고 수정할 수 있는 초기 사용자에게만 실험적 가치가 있다.
- 유머러스하게 표현하면, 어떤 문제든 직접 고칠 준비가 된 “타락한 Rust 애호가(degenerate Rust enjoyer)”라면 예외가 될 수 있다.
6.2. 기술 선택에 주는 시사점
-
언어보다 경계 설계가 중요하다
- Rust를 브라우저에서 끝까지 실행하는 것이 항상 최선의 목표는 아니다.
- 단순한 반응성만 JavaScript로 보내고, 복잡한 처리와 데이터 접근은 서버에 두면 Rust의 강점과 웹 플랫폼의 현실을 함께 활용할 수 있다.
-
보일러플레이트 제거의 효과
- procedure가 엔드포인트와 요청 코드를 자동 생성하면 서버 API와 클라이언트 호출 코드의 중복이 줄어든다.
- shard가 HTML 일부를 독립 갱신하면 상태 관리와 DOM 업데이트를 위한 별도 프런트엔드 계층이 작아진다.
- memoise가 요청 단위 캐시와 single-flight를 제공하면 성능 최적화를 기능별로 직접 조립할 필요가 줄어든다.
-
실험적 프레임워크를 평가하는 기준
- 기능 수보다 서버·클라이언트 경계를 얼마나 명료하게 만드는지 살펴봐야 한다.
- WebAssembly, JavaScript 상호운용, 서버 왕복 지연, breaking change 위험을 실제 애플리케이션의 요구와 함께 평가해야 한다.
- Topcoat는 기술적으로 매력적인 방향을 보여주지만, 안정적인 릴리스와 생태계가 확보되기 전까지는 개인 프로젝트·학습·프로토타입에 적합하다.
주요 발언 모음
“Rust on the web이 좋아지고 있다는 판단은 맞았지만, 내가 생각했던 이유 때문은 아니었다.”
“Topcoat는 상호작용을 위해 WebAssembly를 사용하지 않는다.”
“런타임 표현식은 단순한 클라이언트 사이드 상호작용에 더 적합하도록 의도적으로 제한되어 있다.”
“Procedure는 서버에서 실행하기 적합한 작업을 일반적인 Rust 비동기 함수처럼 호출하게 해준다.”
“Shard를 사용하면 signal 변화에 반응해 다시 렌더링하는 페이지의 범위를 좁힐 수 있다.”
“여러 signal 변화를 한 틱에 합치고 이전 요청을 중단해 최신 인자만 승자로 만든다.”
“Topcoat는 무거운 프런트엔드 프레임워크 없이도 인터랙티브 웹 애플리케이션을 만들게 한다.”
“Topcoat는 아직 실험 단계이므로 프로덕션 웹 애플리케이션에 사용하는 것은 권장하지 않는다.”
핵심 데이터 & 수치
- 영상 길이: 약 12분 39초(759초)다.
- 실제 발행일: 2026-09-27이다.
- Neon 사용 기간: 프로덕션 운영 기능을 평가하는 맥락에서 약 2년 동안 사용했다고 설명한다.
- 중복 조회 변화:
memoise를 추가하면 하나의 요청에서 두 번 발생하던 동일 사용자 조회가 한 번으로 줄어든다. - 서버 반응성 최적화: signal 변경을 한 틱 단위로 합치고 이전 inflight 요청을 중단해 최신 요청만 적용한다.
- 지원 범위: Topcoat는 page, layout, route, component, procedure, shard 등 여러 서버 핸들러에 request context를 제공한다.
결론 및 시사점
- Rust 웹의 다음 단계는 WebAssembly를 모든 클라이언트 로직에 적용하는 것이 아니라, 서버와 브라우저가 각자 잘하는 일을 나누는 설계에서 나올 가능성이 크다.
- Topcoat는 Rust 표현식의 JavaScript 컴파일,
raw매크로, procedure, shard, server-read signal, memoise를 하나의 서버 우선 모델로 묶는다. - 데이터베이스 연결 같은 공유 리소스는 app context에 두고, 요청 정보와 요청 단위 signal은 request context를 통해 핸들러에 주입한다.
- procedure는 HTTP 경계의 보일러플레이트를 제거하고, shard는 서버 왕복을 필요한 DOM 조각으로 제한한다.
- 서버 왕복에는 지연이 따르지만 signal coalescing과 이전 요청 중단으로 빠른 입력에서 발생하는 경쟁 상태를 관리한다.
memoise는 요청 범위 캐시와 single-flight를 한 줄의 어트리뷰트로 제공해 백엔드 코드의 중복 조회를 줄인다.- 현재 Topcoat는 breaking change가 예상되는 실험 프로젝트이므로 프로덕션 도입보다 프로토타입과 학습에 적합하다.
- Rust 웹 프레임워크를 평가할 때는 WebAssembly 사용 여부만 보지 말고, JavaScript 생태계와의 연결 비용·서버 렌더링 모델·상태 갱신 범위·운영 안정성을 함께 확인해야 한다.
핵심 요약 (20줄)
- Rust 웹 개발의 개선을 이끈 핵심 변화는 기존 WebAssembly 프레임워크가 아니라 Topcoat의 등장이다.
- Topcoat는 서버 사이드 렌더링과 API 엔드포인트와 Tailwind CSS와 클라이언트 상호작용을 한 프레임워크에 묶는다.
- 컴포넌트 함수와
view매크로와 signal 추상화는 다른 Rust 웹 프레임워크와 비슷한 개발 경험을 제공한다. - 초기 마크업과 런타임 표현식은 서버에서 처리되어 첫 화면을 빠르게 구성한다.
- 브라우저에서 실행할 런타임 Rust 표현식은 JavaScript로 컴파일된다.
- JavaScript 컴파일은 WebAssembly와 브라우저 API 사이의 상호운용 마찰을 줄인다.
raw매크로를 사용하면 Rust 표현식 안에서 원시 JavaScript와 JavaScript 라이브러리를 호출할 수 있다.- 런타임 표현식의 Rust 문법 부분집합 제한은 복잡한 로직을 서버로 보내기 위한 의도적인 경계다.
procedure어트리뷰트는 async 서버 함수를 클라이언트의 일반적인 Rust async 호출처럼 노출한다.- Topcoat는 procedure마다 HTTP 엔드포인트와 클라이언트 요청 코드를 자동으로 생성한다.
- Neon 데이터베이스 브랜칭은 데모와 리뷰 앱과 개발 환경과 프로덕션 데이터 디버깅을 격리한다.
- app context는 데이터베이스 풀처럼 단일 요청보다 오래 살아야 하는 공유 리소스를 보관한다.
- request context는 HTTP 요청 정보와 서버 signal과 app context 조회라는 세 가지 역할을 맡는다.
shard는 인자를 받아 HTML view를 반환하는 서버 함수이며 독립 HTTP 엔드포인트를 가진다.- signal이 바뀌면 shard가 서버에서 HTML을 다시 렌더링하고 결과를 해당 DOM 조각에 병합한다.
- 전체 page 재렌더링과 달리 shard는 갱신 범위를 좁혀 서버 반응성의 비용을 낮춘다.
- Topcoat는 한 틱의 signal 변경을 합치고 이전 요청을 중단해 최신 요청의 결과만 적용한다.
memoise는 request context를 받는 동기·비동기 함수의 결과를 인자별로 요청 동안 캐시한다.- module routing과 asset bundling과 CLI와 server-push
live기능이 서버 우선 개발 경험을 보완한다. - Topcoat는 매력적인 방향을 제시하지만 실험 단계이므로 안정성이 확보되기 전에는 프로덕션 사용을 피해야 한다.
