Rust WebアプリケーションにおけるAskamaとTeraを用いたシームレスなサーバーサイド・テンプレート
Lukas Schneider
DevOps Engineer · Leapcell

はじめに
動的なWebアプリケーションの構築では、多くの場合、サーバー上でHTMLコンテンツを生成し、それをクライアントに送信する必要があります。サーバーサイド・レンダリング(SSR)として知られるこのプロセスは、初期ページの読み込みパフォーマンス、SEO、およびJavaScriptが無効になっているユーザーのためのフォールバックの提供など、さまざまな理由で極めて重要です。Rustのエコシステムでは、Actix WebやWarpのようなフレームワークは堅牢なWebサービスを構築するための優れた基盤を提供しますが、動的なHTMLのレンダリング方法を固有に規定しているわけではありません。そこで、専用のテンプレートエンジンが登場し、定義済みの HTML 構造にデータを注入するための強力な方法を提供します。この記事では、Rustで高く評価されている2つのテンプレートエンジン、AskamaとTeraについて掘り下げ、それらがRust Webアプリケーションで効率的かつ表現力豊かなサーバーサイド・テンプレート・レンダリングをどのように可能にするかを実証します。
Rustにおけるサーバーサイド・テンプレートの理解
Askama と Tera の詳細に入る前に、サーバーサイド・テンプレートに関連するいくつかのコアコンセプトを明確にしましょう。
テンプレートエンジン: テンプレートエンジンは、静的なテンプレートファイル(通常は特別なプレースホルダーやロジックを含むHTML)を動的なデータと組み合わせて、最終的なドキュメント(通常はHTMLページ)を生成できるソフトウェアコンポーネントです。
サーバーサイド・レンダリング (SSR): Webページをサーバーでレンダリングし、完全に形成された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テンプレートに大きく触発されています。テンプレート継承、マクロ、フィルター、カスタム関数など、豊富な機能セットを提供しており、複雑なWebアプリケーションに非常に適しています。Askamaのコンパイル時アプローチとは異なり、Teraは実行時にテンプレートを処理するため、さまざまなソースからテンプレートをロードしたり、開発中にホットリロードしたりするなどの特定のシナリオでより柔軟性があります。
Tera による実装
前の例を Tera を使用するように適応させてみましょう。
まず、Cargo.toml
に Tera と Actix Web を追加します。
[dependencies] actix-web = "4" tera = "1" serde = { version = "1", features = ["derive"] } serde_json = "1"
Maraは通常、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 rendering error: {}", 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!("Parsing error(s): {}", 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 のようなテンプレートエンジンを使用したサーバーサイド・テンプレートは、さまざまな Web アプリケーションパターンに役立ちます。
- 従来のWebアプリケーション: UI のほとんどがサーバーでレンダリングされ、高速な初期ページ読み込みと優れた SEO を保証するフルスタックアプリケーション。
- ハイブリッドアプリケーション(SSR + ハイドレーション): 高速な初回ペイントのための完全にレンダリングされた初期 HTML を提供し、その後、クライアントサイド JavaScript によってインタラクティビティのために「ハイドレーション」される。
- Eメールテンプレート: 定義済みのレイアウトにユーザー固有のデータを動的に挿入して、リッチな HTML Eメールを生成する。
- 静的サイトジェネレーター (SSG): 主な目的ではありませんが、テンプレートエンジンは多くの SSG の中核であり、データファイルとテンプレートを処理して静的 HTML を生成します。
結論
Askama や Tera のようなエンジンを使用して Rust Web アプリケーションにサーバーサイド・テンプレートを統合することは、動的で保守しやすく、堅牢なユーザーインターフェースを構築する能力を大幅に向上させます。Askama はコンパイル時の保証を通じて比類のない型安全性とパフォーマンスを提供し、Tera はより複雑なテンプレートニーズに対して豊富で柔軟な機能セットを提供します。それぞれの強みを理解することで、動的なコンテンツをシームレスにレンダリングするための適切なツールを選択でき、Rust Web 開発体験を強力で楽しいものにすることができます。最終的に、Askama または Tera を選択することで、高性能な Rust バックエンドから直接魅力的な Web 体験を作成できるようになります。