본문 바로가기
리뷰/테크 블로그

Tokio 생태계에 등장한 새로운 웹 풀스택 프레임워크, Topcoat

by lms0806 2026. 7. 26.
728x90
반응형

최근에 tokio 생태계에 새로운 녀석이 등장했습니다.

 

바로 Topcoat이라는 웹 풀스택 프레임워크입니다.

Topcoat이란?

Topcoat는 서버 중심 렌더링으로, 서버에서 HTML 조각을 렌더링하고 메타데이터로 "반응형 지시문"을 추가하는 방식으로 더해집니다. (HTMX와 유사)

 

Topcoat로 만든 "hello wolrd"앱 입니다.

#[tokio::main]
async fn main() {
    topcoat::start(Router::builder().discover().build()).await.unwrap();
}

#[page("/")]
async fn home() -> Result {
    view! {
        <!DOCTYPE html>
        <html>
            <head>
                <title>"Hello world"</title>
                topcoat::dev::script()
            </head>
            <body>
                hello(name: "World")
            </body>
        </html>
    }
}

#[component]
async fn hello(name: &str) -> Result {
    view! {
        <h1>"Hello, " (name) "!"</h1>
    }
}

만든 이유

현재 가장 풍부한 웹 생태계는 JS, Ruby, PHP처럼 생산성에 큰 비중을 두는 언어들입니다.

 

그러나, AI로 인하여 해당 계산법을 완전히 바뀌게 되었습니다.

 

Rust를 모르는 소프트웨어 엔지니어들이 AI와 함께 Rust로 새로운 무언가를 쉽게 만드는 것이 가능해졌습니다.

 

Rust는 매우 뛰어난 속도와 신뢰성을 갖추고 있습니다.

 

Rust로도 생산성 있게 만들기 위해 필요한 것이 풍부한 라이브러리 생태계입니다.

 

가장 먼저, tokio에서는 Toasty라는 ORM을 만들었습니다.

 

그 다음 단계가 웹 프레임워크로 Topcoat이 등장하게 됩니다.

WASM 없음

LeptosDioxus 같은 프레임워크는 코드를 WASM으로 컴파일해 브라우저에서 실행하는 방식으로 Rust를 사용해 매우 상호작용이 풍부한 웹 애플리케이션을 만드는 데 탁월합니다.

 

그러나, 별도의 타깃으로 컴파일하고, 번들 크기와 분할을 신경쓰고, 클라이언트/서버 경계 너머로 데이터를 직렬화하는 일은 부담이 됩니다.

 

그러나, Topcoat는 단순한 접근을 추구합니다. 모든 마크업은 서버에서 렌더링되므로 컴포넌트는 async될 수 있고, 데이터베이스에 접근하거나, 사용자 권한을 안전하게 검증할 수 있습니다.

 

여기에 매크로를 사용해 완전히 타입 검사된 Rust 표현식의 일부를 JS로 교차 컴파일하므로 WASM을 건드리지 않고 Rust 안에 머무를 수 있습니다.

 

클라이언트 상태가 바뀔 때마다 UI의 전체 일부를 서버에서 다시 렌더링하고, 페이지에서 필요한 부분만 교체할 수도 있습니다.

#[component]
async fn search() -> Result {
    view! {
        signal query = String::new();

        // Write the current text input into the `query` signal:
        <input @input=$(|e: Event| query.set(e.target.value))>

        // Updates as the user types.
        search_results(query: $(query.get()))
    }
}

// Shards are a special type of component that exposes an API endpoint from your router.
#[shard]
async fn search_results(cx: &Cx, query: String) -> Result {
    // This function runs on the server. It can access the database asynchronously.
    view! {
        <ul>
            for product in search_products(cx, &query).await? {
                <li>(product.name)</li>
            }
        </ul>
    }
}

UI 개발

사용자 지정 폰트, 스타일시트, 이미지 등을 불러오기 위해 Topcoat는 asset 매크로를 사용합니다.

 

빌드 시점에 Topcoat CLI는 모든 자산을 수집하거나 다운로드 하여 하나의 디렉터리에 저장합니다.

const FERRIS: Asset = asset!("./ferris.png");

view! { <img src=(FERRIS)> }

좋은 디자인에는 각자의 폰트와 아이콘이 필요하여 FontsourceIconify를 사용할 수 있는 방대한 무료 폰트 및 아이콘 라이브러리르 제공합니다.

// Loads the "Roboto" web font from Fontsource.
const ROBOTO: Font = fontsource_font!(ROBOTO);

// Creates a Rust module containing the "feather" icon set.
iconify::include!("feather");

컴포넌트 라이브러리는 디자인이 바뀌기 시작하면 종종 유연성 부족 문제가 나타납니다. Topcoat의 내장 컴포넌트 라이브러리는 Tailwind를 기반으로 하며, 미리 준비된 컴포넌트를 여러분의 소스 디렉터리로 직접 복사합니다.

#[component]
async fn delete_card() -> Result {
    view! {
        card(
            card_header(
                card_title("Delete workspace")
                card_description("This permanently removes the workspace and all of its data.")
            )
            card_footer(
                attrs: attributes! { class="justify-end" },
                button(variant: ButtonVariant::Ghost, "Cancel")
                button(variant: ButtonVariant::Destructive, "Delete workspace")
            )
        )
    }
}

동작의 지역성

우리는 데이터를 컴포넌트 인자로 아래로 전달하기보다, 컴포넌트가 스스로 데이터를 가져오도록 권장합니다:

#[component]
async fn user_profile(cx: &Cx, user_id: &str) -> Result {
    // Only this component knows what user data it needs.
    let user = load_user(cx, user_id).await?;
    view! {
        <h1>(user.name)</h1>
        ...
    }
}

데이터를 여러 번 가져오는 일을 피하기 위해 Topcoat에는 React의 cache에서 영감을 받은 요청 수준 메모이제이션이 내장되어 있습니다:

#[memoize]
async fn load_user(cx: &Cx, user_id: &str) -> Result<User> {
    // This database call is made only once per unique `user_id`.
    db(cx).load_user_by_id(user_id).await
}

Axum은?

Axum은 서버 애플리케이션 구축을 위한 또 다른 Tokio 호스팅 크레이트입니다.

 

Axum은 저수준 HTTP 라우터로, HTTP API 엔드포인트를 쉽게 만들 수 있게 해줍니다.

 

Axum으로도 반응형 웹앱을 만들 수 있지만, 많은 상용구 코드와 설정이 필요합니다. Topcoat의 목표는 그 사용구를 제거하는 것이지만, 저수준 HTTP 엔드포인트만 만들고 싶다면 Axum을 선택해야 합니다.

느낀 점

저는 대략 1년전부터 Rust의 매력에 빠져 학습을 시작하였고, 현재는 웹 백엔드로 Axum을 써서 개발을 진행하고 있습니다.

 

처음에 프로젝트를 진행할 때, 여러 라이브러리 및 프레임워크들에 대하여 고민이 있어 여러 사람들과 이야기를 나누었었습니다. 그 중, 몇몇 분들은 Rust로 풀스택을 하는 이유 중에 하나가 JS에 대한 거부감도 포함되어 있었습니다.

 

Topcoat가 이런 분들에게 많은 도움이 될 것이라고 생각합니다.

 

저 또한, 개인적인 프로젝트를 또 하나 진행하게 된다면, Topcoat을 하나의 고려사항으로 두고 생각을 해볼거 같습니다.

 

출처 : https://tokio.rs/blog/2026-07-22-announcing-Topcoat

728x90
반응형

댓글