当然!我们来探讨一个虽然在现代 Web 开发中不那么主流,但在某些场景下(如快速构建管理后台、SEO 友好的页面)依然非常有用的技术:MVC(模型-视图-控制器)与服务端渲染。这就像是让你的 NestJS 应用不仅能提供“原材料”(JSON API),还能亲自“烹饪和摆盘”,直接端上一道完整的“大餐”(渲染好的 HTML 页面)。
为你的应用搭建一个华丽的“舞台”:NestJS MVC 模式与服务端渲染
想象你的应用是一位电影制片人。
在之前的所有章节中,这位制片人主要负责提供“剧本和原始素材”(JSON API)。他把这些素材交给“后期特效团队”(前端框架,如 React, Vue, Angular),由他们来最终剪辑、渲染成观众能看到的电影。这种模式被称为前后端分离,是现代 Web 开发的主流。
但有时,制片人也需要亲自执导一些“短片”或“预告片”,比如:
- 一个简单的项目介绍网站。
- 一个内部使用的后台管理界面。
- 一些需要被搜索引擎(如百度、谷歌)完美抓取的页面。
在这些场景下,我们不希望再动用庞大的“后期特效团队”,而是希望制片人(NestJS 应用)能直接在服务器端,将“剧本”和“舞台布景”结合,渲染成一帧完整的画面(HTML 页面),然后直接发送给观众(浏览器)。
这个“制片人亲自执导”的过程,就是服务端渲染 (Server-Side Rendering, SSR),而它所遵循的经典模式,就是 MVC:
- 模型 (Model):剧本和素材。这是你的数据和业务逻辑,比如从数据库中获取的用户信息。
- 视图 (View):空的舞台布景。这是一个带有特殊占位符的 HTML 模板文件(如
index.hbs
),它定义了页面的基本结构。 - 控制器 (Controller):导演。他获取到剧本(数据),告诉演员(数据)在舞台的哪个位置(填充到模板的占位符中),然后大喊一声 "Action!",舞台灯光亮起(模板被渲染成最终的 HTML),呈现给观众。
1. 第一步:搭建“舞台” (配置视图引擎)
NestJS 是一个与视图无关的框架,它不强制你使用任何特定的模板引擎。你需要自己选择并安装一个“舞台搭建团队”。Handlebars (hbs
) 是一个非常流行和简单的选择。
第一步:安装依赖
npm install hbs
第二步:在 main.ts
中配置“舞台”
我们需要告诉 NestJS 在哪里找到我们的“舞台布景”(模板文件),以及该用什么工具来“搭建”它。
src/main.ts
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express'; // 注意!需要导入这个类型
import { join } from 'path';
import { AppModule } from './app.module';
async function bootstrap() {
// 1. 使用 NestExpressApplication 类型,因为它提供了专门用于 Express 的方法
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// 2. 配置静态资源目录 (CSS, JS, 图片)
// 告诉 NestJS,所有在 'public' 文件夹下的文件,都可以通过根 URL 直接访问
app.useStaticAssets(join(__dirname, '..', 'public'));
// 3. 配置视图文件的基础目录
// 告诉 NestJS,我们所有的模板文件都放在 'views' 文件夹下
app.setBaseViewsDir(join(__dirname, '..', 'views'));
// 4. 指定使用 hbs 作为视图引擎
app.setViewEngine('hbs');
await app.listen(3000);
}
bootstrap();
第三步:创建你的第一个“舞台布景” (View Template)
在项目根目录下,创建 views
文件夹,并在其中创建一个 index.hbs
文件。
views/index.hbs
<!DOCTYPE html>
<html>
<head>
<title>My Awesome App</title>
</head>
<body>
<h1>{{ title }}</h1>
<p>{{ message }}</p>
</body>
</html>
``` * `{{ title }}` 和 `{{ message }}`
就是**占位符**。导演(Controller)稍后会用真实的数据来替换它们。 ### 2.
第二步:“导演”就位 (在 Controller 中渲染视图)
现在,我们的“导演”——`Controller`——要登场了。他使用一个特殊的“导演指令”——`@Render()`
装饰器。 **`src/app.controller.ts`** ```typescript import { Controller, Get,
Render } from '@nestjs/common'; @Controller() export class AppController { // 1.
使用 @Render('index') 装饰器 // 这告诉导演:“这个动作最终要使用名为 'index.hbs'
的舞台布景” @Get() @Render('index') root() { // 2.
返回一个对象,这个对象的键值对将被用来填充模板 // 'title' 的值会替换
{{title}},'message' 的值会替换 {{message}} return { title: 'NestJS MVC 模式',
message: '欢迎来到我的服务端渲染页面!' }; } }
现在,启动应用并访问 http://localhost:3000
。 你不会再看到原始的 "Hello World!" 字符串,而是会看到一个被完整渲染好的 HTML 页面,<h1>
和 <p>
标签里的内容已经被我们从 Controller
返回的对象动态填充了!
工作流程回顾:
- 浏览器请求
/
。 - NestJS 路由匹配到
AppController
的root()
方法。 @Render('index')
装饰器被激活,NestJS 知道它需要渲染一个视图。root()
方法执行,并返回{ title: '...', message: '...' }
对象(“剧本数据”)。- NestJS 将这个数据对象和
index.hbs
模板一起交给 Handlebars 视图引擎。 - Handlebars 引擎将数据填充到模板的占位符中,生成最终的 HTML 字符串。
- NestJS 将这个 HTML 字符串作为响应,发送给浏览器。
3. 企业级方案的思考:当“小剧场”无法满足“百老汇”
NestJS 提供的基础 MVC 功能,非常适合制作一些简单的、非交互密集型的页面,比如:
- 内部管理后台 (CRUD 界面)
- 文档站点
- 邮件模板渲染
但是,对于复杂的、交互性强的、需要精细状态管理的现代“百老汇”级应用(比如淘宝、美团这样的应用),仅仅使用 hbs
这样的传统模板引擎是远远不够的。
原生方案的痛点:
- 缺乏组件化:用
hbs
构建复杂 UI 是一场噩梦。 - 无状态管理:没有像 Redux, Vuex 这样成熟的状态管理方案。
- 前端开发体验差:无法享受现代前端工程化的各种便利(热更新、CSS 模块化等)。
中国企业级方案的主流选择:
方案一:“前后端分离” (主流模式)
这是目前绝对的主流。企业会将团队分为前端和后端。
- 后端团队 (使用 NestJS):只负责提供高性能、高可用的 RESTful API 或 GraphQL API。他们就是纯粹的“素材供应商”,完全不关心最终的“电影画面”。
- 前端团队 (使用 Vue / React):构建一个完全独立的单页应用 (SPA)。这个应用部署在独立的服务器上(如 Nginx 或 Vercel),它通过 HTTP 请求从 NestJS 后端获取数据,然后在浏览器端负责所有的渲染和交互。
优点:
- 职责清晰:前后端团队可以并行开发,互不干扰。
- 技术栈灵活:前端可以自由选择最适合的框架。
- 可扩展性强:后端可以为多个客户端(Web, iOS, Android, 小程序)提供服务。
方案二:“同构渲染” (Isomorphic SSR)
对于那些对 SEO (搜索引擎优化) 和 首屏加载速度 有极致要求的应用(如新闻门户、电商网站),会采用更高级的“同构渲染”方案。
- 核心思想:使用同一个框架(通常是 React 或 Vue)来同时编写前端和后端的渲染逻辑。
- 技术栈:Next.js (基于 React) 或 Nuxt.js (基于 Vue) 是这个领域的王者。
与 NestJS 的集成模式: 在这种模式下,Next.js/Nuxt.js 应用本身就是主服务器。
- 当一个请求到达 Next.js 服务器时。
- Next.js 在其服务端的生命周期函数(如
getServerSideProps
)中,会作为一个 HTTP 客户端,去调用那个独立部署的 NestJS API 服务来获取数据。 - 拿到数据后,Next.js 在服务器端使用 React/Vue 将页面渲染成完整的 HTML,并将其发送给浏览器(实现了快速首屏和 SEO)。
- 页面到达浏览器后,前端的 React/Vue 代码会“接管”(Hydration) 这个页面,使其变成一个功能齐全的单页应用。
Next.js 页面文件 (pages/posts/[id].js)
的伪代码:
export async function getServerSideProps(context) {
const { id } = context.params;
// 在服务端,使用 axios 或 fetch 调用 NestJS API
const response = await axios.get(`https://my-nestjs-api.com/posts/${id}`);
const postData = response.data;
// 将获取的数据作为 props 传递给页面组件
return { props: { postData } };
}
// 页面组件会接收到 postData 并渲染
function PostPage({ postData }) {
return <h1>{postData.title}</h1>;
}
这种模式结合了服务端渲染和单页应用的优点,是构建大型、高性能 Web 应用的终极方案之一。
总结
NestJS 的 MVC 功能为你提供了一个快速构建服务端渲染页面的便捷工具。
- 何时使用? 当你需要制作简单的、内容驱动的页面,或者一个内部管理后台时,它是一个很好的选择。
- 如何实现? 安装一个视图引擎(如
hbs
),在main.ts
中进行配置,然后在Controller
中使用@Render()
装饰器。 - 企业级的选择是什么? 对于复杂的应用,“前后端分离” 是最务实、最主流的选择。而对于有极致性能和 SEO 需求的应用,使用 Next.js/Nuxt.js 与 NestJS API 结合的“同构渲染” 方案则是更前沿、更强大的架构。