# 渲染方式对比
# CSR(客户端渲染)
系统类网页应用渲染方式,即组装页面这个步骤,是在客户端进行,服务端提供异步数据请求的后端服务。
# 优点
- 适合有大量动态数据的网站,即常规后台等需要 api 获取数据的网站类型
- 构建时间短
# 缺点
- 没有良好的 seo(后台网站不对公众开放,不影响)
- 页面从请求到首屏时间长
- 对部署前端的服务器压力较大
# 框架
- React
- Vue
- Angular
# SSR(服务端渲染)
门户类网站渲染方式
# 优点
- 有良好 seo
- 页面从请求到首屏时间较短,基本页面可以很快展示
# 缺点
- 只适合使用少量动态数据或完全不使用动态数据
- 构建时间较长
- 对部署前端的服务器压力较大
# 框架
- Next.js(React+Node.js)
- Nuxt.js(Vue+Node.js)
- Angular universal(Angular+Express.js)
- Nest.js(Node.js+Express.js)
# SSG(静态网站生成)
博客及文档类网站渲染方式
# 优点
- 优秀的 seo
- 页面从请求到首屏时间极短
- 对部署前端的服务器压力小
# 缺点
- 构建时间长
- 不能使用动态数据,每次修改都需要重新构建
# 框架
- Vuepress
- Jekyll
- Gatsby
- Hugo
# Angular universal 入门
# 安装
官网链接:https://angular.io/guide/universal
假设当前已有一个标准的 Angular 应用,安装 ssr 则需要先安装一个服务,这里是 express。
ng add @nguniversal/express-engine
安装前后目录对比:
安装前 | 安装后 |
---|---|
主要分为两部分,一部分是修改了配置,另一部分是创建了 server 端代码
客户端方面的配置改动,是把原本的应用打包输出放在了二级文件夹下
服务端则是完全重新生成了一套配置,用于 ssr 项目的开发
在脚本中添加了 ssr 的开发,运行,打包和预渲染命令
import "zone.js/dist/zone-node";
import { ngExpressEngine } from "@nguniversal/express-engine";
import * as express from "express";
import { join } from "path";
import { AppServerModule } from "./src/main.server";
import { APP_BASE_HREF } from "@angular/common";
import { existsSync } from "fs";
// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
const server = express();
const distFolder = join(process.cwd(), "dist/ssr-project/browser");
const indexHtml = existsSync(join(distFolder, "index.original.html"))
? "index.original.html"
: "index";
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine(
"html",
ngExpressEngine({
bootstrap: AppServerModule
})
);
server.set("view engine", "html");
server.set("views", distFolder);
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get(
"*.*",
express.static(distFolder, {
maxAge: "1y"
})
);
// All regular routes use the Universal engine
server.get("*", (req, res) => {
res.render(indexHtml, {
req,
providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }]
});
});
return server;
}
function run(): void {
const port = process.env["PORT"] || 4000;
// Start up the Node server
const server = app();
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = (mainModule && mainModule.filename) || "";
if (moduleFilename === __filename || moduleFilename.includes("iisnode")) {
run();
}
export * from "./src/main.server";
服务端最核心的则是根目录的 server.ts 文件,这里做了 Angular 页面与 express 服务的绑定,以及 express 服务的启动代码
main.ts 和 app.module.ts 都对服务端逻辑创建了一份新文件,当遇到服务端缓存数据或其他类型的需要操控服务端的逻辑,就需要在这里配置。如果只是普通的 ssr 网站,到这一步后则与开发常规 Angular 应用一样,正常开发即可
# 常见问题
# NG-ZORRO 一起使用
官网链接:https://ng.ant.design/docs/universal/zh
文档中有两种方式可以选择,一种较为复杂,需要自己安装 ng-zorro 以后,然后修改服务端文件;另一种则是直接 clone 一个模板项目,修改项目名即可使用。
# 区分服务端逻辑与客户端逻辑
constructor(
@Inject(PLATFORM_ID) private platformId: Object
) {}
func() {
if (isPlatformBrowser(this.platformId)) {
// do something
}
}
# 跨域配置
跨域配置分为两种情况,分别是开发模式和生产模式
# 开发模式
"dev:ssr": "ng run xw_offical:serve-ssr"
因为 ssr 会有服务端请求和客户端请求同时存在,客户端请求代理即 Angular 的 proxy 文件中配置,而服务端需要在 express 的 server.ts 文件中添加中间件。
import { createProxyMiddleware } from "http-proxy-middleware";
const server = app().use(
"/api",
createProxyMiddleware({
target: "http://ip:port/",
secure: false,
logLevel: "debug",
changeOrigin: true
})
);
# 生产模式
"serve:ssr": "node dist/xw_offical/server/main.js"
生产模式会将客户端请求拦截并从服务端发送,因此只需要 server.ts 文件中的中间件即可。