在 Rust Web 项目里渲染 HTML,有人用 askama(编译期类型检查),有人用 Minijinja(Jinja2 语法、运行时灵活)。博客、管理后台、邮件模板这类「结构多变、热更新友好」的场景,Minijinja 往往更顺手。
基本集成
[dependencies]
minijinja = { version = "2", features = ["loader"] }
use minijinja::{context, Environment};
fn render_index(posts: &[Post]) -> String {
let mut env = Environment::new();
env.add_template("index.html", include_str!("../templates/index.html"))
.unwrap();
let tpl = env.get_template("index.html").unwrap();
tpl.render(context! {
posts => posts,
site => site_config(),
}).unwrap()
}
include_str! 把模板编进二进制,部署简单;开发期也可改成从 templates/ 目录加载方便热重载。
与 Jinja2 相通的语法
{% for post in posts %}
<article>
<h2><a href="{{ post.url }}">{{ post.title }}</a></h2>
<p>{{ post.excerpt }}</p>
</article>
{% else %}
<p>暂无文章</p>
{% endfor %}
{% if filter_category %}
<span>当前分类:{{ filter_category }}</span>
{% endif %}
前端同学如果熟悉 Flask/Django,几乎零学习成本。
include 拆分公共片段
{% include "head_meta.html" %}
{% include "admin_nav.html" %}
导航、页脚、meta 标签各一份,管理后台六个页面共用 admin_nav.html,改一处全局生效——这也是「菜单栏不跳」的基础。
自定义过滤器
URL 编码、日期格式化常在 Rust 侧注册:
env.add_filter("urlencode", |s: String| -> String {
urlencoding::encode(&s).into_owned()
});
模板里:{{ post.category | urlencode }}
复杂逻辑放 Rust,模板只做展示,边界清晰。
管理页 vs 博客页
| 类型 | 数据来源 | 模板特点 |
|---|---|---|
| 博客首页 | 内存中的 Post 列表 | 循环、分页、侧栏 |
| 文章详情 | 单篇 HTML 正文 | {{ post.content_html | safe }} |
| 管理后台 | API + 少量 SSR | 表单、表格、统一 layout |
注意:safe 只对可信 HTML 使用(你自己 Markdown 渲染出来的),用户评论内容必须转义。
错误处理
pub fn render(env: &Environment, name: &str, ctx: Value) -> String {
env.get_template(name)
.and_then(|t| t.render(ctx))
.unwrap_or_else(|e| {
tracing::error!("template {name}: {e}");
"页面渲染失败".into()
})
}
生产环境不要 unwrap() 把堆栈吐给用户;记日志 + 友好 500 页。
和纯前端的取舍
- SSR 模板:首屏快、SEO 友好、后台页简单
- SPA:交互复杂时更合适
个人博客「文章列表 + 详情 + 轻量后台」,SSR + 少量 fetch(评论、分页)通常是甜点位。
小结
Minijinja 的价值在于:Rust 的类型与性能 + 模板语言的表达力。把公共片段拆好、过滤器放在 Rust、危险 HTML 管住,就能用很少代码维护一整套站点与管理界面。
如果你已经在用 Axum,加上 Minijinja 往往就是再加一个 Environment 和几份 HTML——比重构到 React 便宜得多。
评论