前端模板(如jsRender、Handlebars)仅支持客户端字符串替换,无法执行服务端逻辑(如if user.is_admin),只能基于传入的JSON数据渲染;后端模板(如EJS、Jinja2)可直接访问request、session等服务端上下文,天然支持动态SEO与权限控制。

HTML模板(前端)不支持服务端逻辑嵌入
前端用的 jsRender、Handlebars 或原生 template 标签,本质是纯客户端字符串替换或 DOM 操作。它们无法执行 if user.is_admin 这类服务端判断——因为变量只在浏览器里存在,没经过后端上下文。
常见错误现象:把后端模板语法(如 )直接抄到前端模板里,结果整个模板被当作文本渲染,甚至报 Uncaught SyntaxError。
- 前端模板能用的只有传入的 JSON 数据字段,比如
{{name}}、{{#each items}} - 条件/循环必须基于已有数据结构,不能调用后端函数或访问 session、cookie 等服务端状态
- 若需权限控制,得提前由后端 API 返回
can_edit: true这类布尔字段,前端再据此渲染
后端模板(如 EJS、Jinja2、Thymeleaf)可直接访问服务端上下文
后端模板引擎运行在服务器进程内,天然能读取 request、session、数据库查询结果、配置项等。一个 在 EJS 里合法,在浏览器里根本不会执行。
使用场景典型如:管理后台首页根据角色显示不同菜单;订单页根据支付状态展示「取消」或「发货」按钮;多语言文案根据 req.locale 动态切换。
立即学习“前端免费学习笔记(深入)”;
- 参数差异明显:
res.render('order', { order, currentUser })中的currentUser是完整对象,含方法和私有属性;前端收到的只是序列化后的扁平 JSON - 性能影响:每次渲染都触发服务端计算,高并发下可能成为瓶颈;但首屏 HTML 完整,无需额外 fetch
- 兼容性上,后端模板与框架强绑定(如 Django 模板不兼容 Express + EJS),迁移成本高
组件复用能力取决于模板引擎设计,而非“前后端”标签
所谓“组件”,在前端模板中靠 registerHelper(Handlebars)、define(jsRender)或自定义指令实现;在后端模板中靠 include、macro(Jinja2)、partial(EJS)支撑。两者都能复用,但机制完全不同。
容易踩的坑:误以为前端模板的 {{> header}} 和后端的 {% include 'header.html' %} 行为一致——其实前者是运行时动态加载字符串,后者是编译期合并模板文件。
- 前端组件依赖 JS 加载顺序,
header模板必须先于使用处定义 - 后端组件在服务启动时已预编译,不存在“未定义”问题,但修改后需重启或热重载支持
- 跨项目复用时,前端组件更易打包成 npm 包;后端组件通常锁死在特定框架生态内
SEO 和首屏内容生成方式根本不同
后端模板渲染出的 HTML 在 HTTP 响应体里就包含全部可见文本,爬虫打开即见内容;前端模板首次返回的是空壳 HTML + JS,真实内容靠 JS 执行后插入 DOM,爬虫若不执行 JS 就抓不到。
这不是“能不能 SEO”的问题,而是“谁负责生成可索引内容”的分工差异。哪怕你用 document.getElementById('app').innerHTML = template(data),只要这个动作发生在 JS 加载之后,对传统爬虫就是不可见的。
- 现代 SSR 框架(如 Next.js、Nuxt)本质是让前端模板在 Node 层执行一遍,属于混合方案,不是纯前端模板
- 纯前端模板项目若要 SEO,必须配合静态站点生成(SSG)或服务端预渲染(SSR),否则只能靠
prerender.io这类第三方快照服务 - 后端模板天然支持动态 SEO 字段(如
<title></title>),但需确保每个路由都走模板流程,而不是返回 JSON



















