Askama와 Tera를 사용한 Rust 웹 애플리케이션의 원활한 서버 측 템플릿 처리
Lukas Schneider
DevOps Engineer · Leapcell

소개
동적인 웹 애플리케이션을 구축하려면 종종 서버에서 HTML 콘텐츠를 생성하여 클라이언트로 보내야 합니다. 서버 측 렌더링(SSR)이라고 하는 이 프로세스는 초기 페이지 로드 성능, SEO 및 JavaScript가 비활성화된 사용자를 위한 대체 제공을 포함한 다양한 이유로 중요합니다.
Rust 생태계에서는 Actix Web 또는 Warp와 같은 프레임워크가 강력한 웹 서비스를 구축하기 위한 훌륭한 기반을 제공하지만, 동적 HTML을 렌더링하는 방법을 본질적으로 알려주지는 않습니다. 지정된 템플릿 엔진이 바로 여기에 있으며, 미리 정의된 HTML 구조에 데이터를 삽입하는 강력한 방법을 제공합니다.
이 문서는 Rust에서 매우 높이 평가되는 두 가지 템플릿 엔진인 Askama와 Tera를 심층적으로 탐구하고, Rust 웹 애플리케이션에서 효율적이고 표현력 있는 서버 측 템플릿 렌더링을 가능하게 하는 방법을 보여줄 것입니다.
Rust에서의 서버 측 템플릿 이해
Askama와 Tera의 구체적인 내용을 살펴보기 전에 서버 측 템플릿과 관련된 몇 가지 핵심 개념을 명확히 해보겠습니다.
템플릿 엔진: 템플릿 엔진은 정적 템플릿 파일(종종 특수 플레이스홀더 또는 로직이 포함된 HTML)을 동적 데이터와 결합하여 최종 문서, 일반적으로 HTML 페이지를 생성할 수 있게 하는 소프트웨어 구성 요소입니다.
서버 측 렌더링(SSR): 웹 페이지를 서버에서 렌더링한 다음 완성된 HTML을 클라이언트로 보내는 프로세스입니다. 이는 클라이언트 측 렌더링(CSR)과 대조됩니다. CSR에서는 최소한의 HTML 페이지가 전송되고 클라이언트의 JavaScript가 데이터를 가져와 UI를 빌드합니다.
컨텍스트/데이터: Rust 애플리케이션에서 템플릿 엔진으로 전달되는 동적 정보입니다. 일반적으로 템플릿에 삽입될 값을 보유하는 구조체 또는 맵 형태입니다.
템플릿 언어: 템플릿 엔진이 이해하고 처리하는 템플릿 파일 내에서 사용되는 특정 구문 및 규칙(예: {{ variable }}
, {% for item in items %}
).
템플릿 엔진을 사용하는 주요 이점은 관심사의 명확한 분리입니다. Rust 코드는 비즈니스 로직과 데이터 검색을 처리하고, 템플릿 파일은 프레젠테이션 레이어에만 집중합니다. 이 분리는 코드베이스를 더 유지 관리하기 쉽고, 읽기 좋으며, 프런트엔드 개발자가 작업하기 쉽게 만듭니다.
Askama: 타입 안전하고 컴파일 타임 검증 템플릿 엔진
Askama는 컴파일 타임 검증 및 성능으로 유명한 Rust의 인기 있는 템플릿 엔진입니다. Rust의 매크로 시스템을 활용하여 컴파일 타임에 템플릿용 코드를 생성하여 우수한 성능과 템플릿 오류의 조기 감지를 이끌어냅니다.
Askama를 사용한 구현
Askama는 많은 개발자에게 친숙한 Jinja와 유사한 구문을 사용합니다. Actix Web 애플리케이션에서 Askama를 사용하는 간단한 예제를 살펴보겠습니다.
먼저 Cargo.toml
에 Askama와 Actix Web을 추가합니다.
[dependencies] actix-web = "4" askama = "0.12"
다음으로 템플릿과 이를 채울 데이터 구조를 정의합니다. Askama는 파생 매크로를 사용하여 구조체를 템플릿 파일과 연결합니다.
// src/templates.rs use askama::Template; #[derive(Template)] #[template(path = "hello.html")] pub struct HelloTemplate<'a> { pub name: &'a str, pub items: Vec<&'a str>, }
이제 프로젝트 루트의 templates
라는 디렉토리에 hello.html
템플릿 파일을 만듭니다.
<!-- templates/hello.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Hello from Askama</title> </head> <body> <h1>Hello, {{ name }}!</h1> <p>Here are some of your favorite things:</p> <ul> {% for item in items %} <li>{{ item }}</li> {% endfor %} </ul> </body> </html>
마지막으로 이를 Actix Web 애플리케이션에 통합합니다.
// src/main.rs use actix_web::{web, App, HttpResponse, HttpServer, Responder}; use askama::Template; // Template 트레잇을 범위로 가져옵니다. use crate::templates::HelloTemplate; mod templates; // Askama 템플릿이 정의된 모듈을 선언합니다. async fn greet() -> impl Responder { let template = HelloTemplate { name: "Rustacean", items: vec!["🦀", "💻", "🚀"], }; HttpResponse::Ok().body(template.render().unwrap()) } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new().route("/", web::get().to(greet)) }) .bind(("127.0.0.1", 8080))? .run() .await }
이 애플리케이션을 실행하고 http://127.0.0.1:8080
으로 이동하면 Rust 코드에서 동적으로 삽입된 'Hello, Rustacean!'과 항목 목록이 포함된 HTML 페이지가 렌더링된 것을 볼 수 있습니다. 여기서 Askama의 장점은 {{ name }}
에 오타(예: {{ namme }}
)를 내면 Rust 컴파일러가 이를 감지하여 런타임 오류를 방지한다는 것입니다.
Tera: 기능이 풍부하고 유연한 템플릿 엔진
Tera는 Rust를 위한 또 다른 강력한 템플릿 엔진으로, 주로 Jinja2 및 Django 템플릿에서 영감을 받았습니다. 템플릿 상속, 매크로, 필터 및 사용자 정의 함수를 포함한 풍부한 기능 세트를 제공하여 복잡한 웹 애플리케이션에 매우 다양하게 사용할 수 있습니다.
Askama의 컴파일 타임 접근 방식과 달리 Tera는 런타임에 템플릿을 처리하므로 다양한 소스에서 템플릿을 로드하거나 개발 중에 핫 리로드하는 등 특정 시나리오에서 더 많은 유연성을 제공합니다.
Tera를 사용한 구현
이전 예제를 Tera를 사용하도록 조정해 보겠습니다.
먼저 Cargo.toml
에 Tera와 Actix Web을 추가합니다.
[dependencies] actix-web = "4" tera = "1" serde = { version = "1", features = ["derive"] } serde_json = "1"
Tera는 일반적으로 serde
와 함께 데이터를 Context
객체로 직렬화하는 데 사용됩니다.
다음으로 tera
를 설정하고 템플릿을 렌더링하는 엔드포인트를 정의합니다. Tera는 구조체에 파생 매크로를 사용하지 않으므로 명시적으로 Context
를 생성합니다.
// src/main.rs use actix_web::{web, App, HttpResponse, HttpServer, Responder}; use serde::{Deserialize, Serialize}; use tera::{Context, Tera}; // 템플릿 데이터를 보유할 구조체 #[derive(Serialize, Deserialize)] struct UserData { name: String, items: Vec<String>, } async fn greet_tera(tera: web::Data<Tera>) -> impl Responder { let mut context = Context::new(); let user_data = UserData { name: "Gopher".to_string(), items: vec!["🏝️".to_string(), "☁️".to_string(), "🔬".to_string()], }; context.insert("user", &user_data); // Tera는 컨텍스트 데이터에 맵과 유사한 구조체를 예상합니다. let rendered = tera.render("hello.html", &context).unwrap_or_else(|err| { eprintln!("Tera 렌더링 오류: {}", err); "Error rendering template".to_string() }); HttpResponse::Ok() .content_type("text/html") .body(rendered) } #[actix_web::main] async fn main() -> std::io::Result<()> { let tera = match Tera::new("templates/**/*.html") { Ok(t) => t, Err(e) => { eprintln!("파싱 오류: {}", e); ::std::process::exit(1) } }; HttpServer::new(move || { App::new() .app_data(web::Data::new(tera.clone())) // Tera 인스턴스를 여러 워커에 공유 .route("/tera", web::get().to(greet_tera)) }) .bind(("127.0.0.1", 8081))? .run() .await }
프로젝트 루트의 templates
디렉토리에 hello.html
템플릿 파일을 만듭니다. Tera에서 중첩된 데이터(예: user.name
)에 액세스하는 방법을 확인하십시오.
<!-- templates/hello.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Hello from Tera</title> </head> <body> <h1>Hello, {{ user.name }}!</h1> <p>Here are some of your favorite things:</p> <ul> {% for item in user.items %} <li>{{ item }}</li> {% endfor %} </ul> </body> </html>
이 애플리케이션을 실행하고 http://127.0.0.1:8081/tera
로 이동하면 Tera로 구동되는 유사한 렌더링 페이지가 표시됩니다.
Askama와 Tera 비교
Askama와 Tera 모두 훌륭한 선택이지만 약간 다른 선호도와 사용 사례를 충족합니다.
- Askama:
- 장점: 컴파일 타임 검증(오류 조기 감지), 생성된 코드로 인한 뛰어난 성능, 템플릿에 구조체를 매핑할 때 강력한 타입 안전성.
- 단점: 동적 템플릿 로딩/재로딩에 덜 유연하고, 템플릿은 애플리케이션과 함께 다시 컴파일됩니다.
- 최적: 성능과 컴파일 타임 보장이 최우선이고 템플릿 구조가 상대적으로 안정적인 애플리케이션.
- Tera:
- 장점: 풍부한 기능 세트(매크로, 상속, 필터), 런타임 템플릿 로딩 및 캐싱, 복잡한 템플릿 계층 구조에 좋음, 개발 중 핫 리로딩.
- 단점: 런타임 오류(템플릿 변수의 오타는 렌더링될 때만 감지됨), Askama보다 성능이 약간 낮음(하지만 여전히 매우 빠름).
- 최적: 광범위한 템플릿 기능, 동적 템플릿 로딩이 필요한 애플리케이션 또는 템플릿에 대한 개발 유연성이 우선시되는 경우.
서버 측 템플릿의 애플리케이션 시나리오
Askama 및 Tera와 같은 엔진을 사용하는 서버 측 템플릿은 다양한 웹 애플리케이션 패턴에서 유용성을 찾습니다.
- 기존 웹 애플리케이션: UI의 대부분이 서버에서 렌더링되어 빠른 초기 페이지 로드와 더 나은 SEO를 보장하는 풀스택 애플리케이션.
- 하이브리드 애플리케이션(SSR + Hydration): 빠른 첫 페인트를 위해 초기 완전 렌더링된 HTML을 제공한 다음, 상호 작용성을 위해 클라이언트 측 JavaScript에 의해 "수화"될 수 있습니다.
- 이메일 템플릿: 사용자별 데이터를 미리 정의된 레이아웃에 동적으로 삽입하여 풍부한 HTML 이메일을 생성합니다.
- 정적 사이트 생성기(SSG): 주요 초점은 아니지만 템플릿 엔진은 많은 SSG의 핵심으로, 데이터 파일과 템플릿을 정적 HTML로 처리합니다.
결론
Askama 및 Tera와 같은 엔진을 사용하는 Rust 웹 애플리케이션에 서버 측 템플릿을 통합하면 동적이고 유지 관리 가능하며 강력한 사용자 인터페이스를 구축하는 능력이 크게 향상됩니다.
Askama는 컴파일 타임 보장을 통해 비교할 수 없는 타입 안전성과 성능을 제공하는 반면, Tera는 더 복잡한 템플릿 요구 사항에 대한 풍부하고 유연한 기능 세트를 제공합니다.
각각의 강점을 이해함으로써 올바른 도구를 선택하여 동적 콘텐츠를 원활하게 렌더링하여 Rust 웹 개발 경험을 강력하고 즐겁게 만들 수 있습니다.
궁극적으로 Askama 또는 Tera를 선택하면 고성능 Rust 백엔드에서 직접 매력적인 웹 경험을 만들 수 있는 역량이 부여됩니다.