Skip to content

当然!我们来探讨一个虽然在现代 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) 是一个非常流行和简单的选择。

第一步:安装依赖

bash
npm install hbs

第二步:在 main.ts 中配置“舞台”

我们需要告诉 NestJS 在哪里找到我们的“舞台布景”(模板文件),以及该用什么工具来“搭建”它。

src/main.ts

typescript
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

html
<!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 返回的对象动态填充了!

工作流程回顾

  1. 浏览器请求 /
  2. NestJS 路由匹配到 AppControllerroot() 方法。
  3. @Render('index') 装饰器被激活,NestJS 知道它需要渲染一个视图。
  4. root() 方法执行,并返回 { title: '...', message: '...' } 对象(“剧本数据”)。
  5. NestJS 将这个数据对象和 index.hbs 模板一起交给 Handlebars 视图引擎。
  6. Handlebars 引擎将数据填充到模板的占位符中,生成最终的 HTML 字符串。
  7. NestJS 将这个 HTML 字符串作为响应,发送给浏览器。

3. 企业级方案的思考:当“小剧场”无法满足“百老汇”

NestJS 提供的基础 MVC 功能,非常适合制作一些简单的、非交互密集型的页面,比如:

  • 内部管理后台 (CRUD 界面)
  • 文档站点
  • 邮件模板渲染

但是,对于复杂的、交互性强的、需要精细状态管理的现代“百老汇”级应用(比如淘宝、美团这样的应用),仅仅使用 hbs 这样的传统模板引擎是远远不够的。

原生方案的痛点:

  • 缺乏组件化:用 hbs 构建复杂 UI 是一场噩梦。
  • 无状态管理:没有像 Redux, Vuex 这样成熟的状态管理方案。
  • 前端开发体验差:无法享受现代前端工程化的各种便利(热更新、CSS 模块化等)。

中国企业级方案的主流选择:

方案一:“前后端分离” (主流模式)

这是目前绝对的主流。企业会将团队分为前端和后端。

  • 后端团队 (使用 NestJS):只负责提供高性能、高可用的 RESTful APIGraphQL 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 应用本身就是主服务器

  1. 当一个请求到达 Next.js 服务器时。
  2. Next.js 在其服务端的生命周期函数(如 getServerSideProps)中,会作为一个 HTTP 客户端,去调用那个独立部署的 NestJS API 服务来获取数据。
  3. 拿到数据后,Next.js 在服务器端使用 React/Vue 将页面渲染成完整的 HTML,并将其发送给浏览器(实现了快速首屏和 SEO)。
  4. 页面到达浏览器后,前端的 React/Vue 代码会“接管”(Hydration) 这个页面,使其变成一个功能齐全的单页应用。

Next.js 页面文件 (pages/posts/[id].js) 的伪代码:

javascript
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 结合的“同构渲染” 方案则是更前沿、更强大的架构。