AngularUniversal在服务端渲染Angular应用,生成静态HTML,提升SEO友好性、移动端性能和首屏加载速度。通过CLI命令添加SSR支持,需替换浏览器API,使用绝对地址处理HTTP请求,支持预渲染和动态路由配置,并利用Title和Meta服务优化SEO。
你知道 Angular Universal 吗?它是提升网站 SEO 表现的重要工具。

长期稳定更新的攒劲资源: >>>点此立即查看<<<
通常,Angular 应用在浏览器中运行,直接在 DOM 上渲染页面并与用户交互。而 Angular Universal 则在服务端完成渲染(即 Server-Side Rendering,SSR),生成静态的应用程序网页,再交给客户端展示。这样做的好处很明显——页面渲染速度更快,能在提供完整交互之前先把内容展示给用户。
本文的示例基于 Angular 14 环境完成,新版本中的部分细节可能有所不同,建议结合 Angular 官方文档参考。
虽然包括 Google 在内的某些搜索引擎和社交媒体声称能对由 Ja vaScript 驱动的单页应用(SPA)进行内容爬取,但实际效果往往差强人意。静态 HTML 网站的 SEO 表现依然明显优于依赖动态渲染的网站。这一点 Angular 官方也持有同样看法——Angular 是 Google 亲生的产品。
通过 Universal,可以生成无 Ja vaScript 的静态版本应用,对搜索爬虫和外部链接导航的支持更加到位。
部分移动设备可能不支持或仅有限支持 Ja vaScript,这会直接影响网站访问体验。在这种场景下,提供一个无 JS 版本的应用显得尤为重要。
用户对首屏加载速度的容忍度极低。根据 eBay 的数据,搜索结果展示速度每提升 100 毫秒,用户“加入购物车”的使用率就能提高 0.5%。这个数字足以说明问题。
有了 Universal,应用的首页会以完整形态的纯 HTML 网页呈现给用户。即使浏览器不支持 Ja vaScript,页面内容也能展示出来。虽然此时网页还不能处理浏览器事件,但通过 routerLink 进行页面导航是没问题的。
这一方案的巧妙之处在于:先用静态页面抓住用户注意力,在用户浏览页面的同时,后台悄无声息地加载整个 Angular 应用,带来极速加载的体验。
Angular CLI 提供了一条捷径,能轻松将普通 Angular 项目转换为支持 SSR 的版本。只需一条命令:
ng add @nguniversal/express-engine
建议在运行此命令前先把所有改动提交到版本控制。该命令会对项目做以下修改:
添加服务端文件:
main.server.ts - 服务端主程序文件app/app.server.module.ts - 服务端应用程序主模块tsconfig.server.json - TypeScript 服务端配置文件server.ts - Express web server 的运行文件修改的文件:
package.json - 添加 SSR 所需的依赖和运行脚本angular.json - 添加开发和构建 SSR 应用所需的配置由于 Universal 应用不在浏览器环境中执行,一些浏览器专属的 API 或功能会失效。最典型的就是服务端无法使用 window、document、na vigator、location 这些全局对象。
Angular 为此专门提供了两个可注入对象,用于在服务端环境中替代这些浏览器对象:Location 和 DOCUMENT。
例如,在浏览器中通常通过 window.location.href 获取当前地址。换成 SSR 后,代码变为:
import { Location } from '@angular/common';
export class AbmNa vbarComponent implements OnInit{
// ctor 中注入 Location
constructor(private _location:Location){
//...
}
ngOnInit() {
// 打印当前地址
console.log(this._location.path(true));
}
}
同样,如果在浏览器中使用 document.getElementById() 获取 DOM 元素,改成 SSR 后如下:
import { DOCUMENT } from '@angular/common';
export class AbmFoxComponent implements OnInit{
// ctor 中注入 DOCUMENT
constructor(@Inject(DOCUMENT) private _document: Document) { }
ngOnInit() {
// 获取 id 为 fox-container 的 DOM
const container = this._document.getElementById('fox-container');
}
}
在 Angular SSR 应用中,HTTP 请求的 URL 地址必须是绝对地址(以 http/https 开头,不能是 /api/heros 这样的相对地址)。官方推荐的做法是把请求的完整 URL 路径设置到 renderModule() 或 renderModuleFactory() 的 options 参数中。不过在 v14 自动生成的代码里,并没有显式调用这两个方法的代码。但通过拦截 HTTP 请求也能达到同样效果。
下面先准备一个拦截器,假设文件位于项目的 shared/universal-relative.interceptor.ts 路径:
import { HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Inject, Injectable, Optional } from '@angular/core';
import { REQUEST } from '@nguniversal/express-engine/tokens';
import { Request } from 'express';
// 忽略大小写检查
const startsWithAny = (arr: string[] = []) => (value = '') => {
return arr.some(test => value.toLowerCase().startsWith(test.toLowerCase()));
};
// http, https, 相对协议地址
const isAbsoluteURL = startsWithAny(['http', '//']);
@Injectable()
export class UniversalRelativeInterceptor implements HttpInterceptor {
constructor(@Optional() @Inject(REQUEST) protected request: Request) { }
intercept(req: HttpRequest, next: HttpHandler) {
// 不是绝对地址的 URL
if (!isAbsoluteURL(req.url)) {
let protocolHost: string;
if (this.request) {
// 如果注入的 REQUEST 不为空,则从注入的 SSR REQUEST 中获取协议和地址
protocolHost = `${this.request.protocol}://${this.request.get('host')}`;
} else {
// 如果注入的 REQUEST 为空,比如在进行 prerender build:
// 这里需要根据实际情况添加自定义的地址前缀
protocolHost = 'https://www.example.com';
}
const pathSeparator = !req.url.startsWith('/') '/' : '';
const url = protocolHost + pathSeparator + req.url;
const serverRequest = req.clone({ url });
return next.handle(serverRequest);
} else {
return next.handle(req);
}
}
}
然后在 app.server.module.ts 文件中把它 provide 出来:
import { UniversalRelativeInterceptor } from './shared/universal-relative.interceptor';
// ... 其他 imports
@NgModule({
imports: [
AppModule,
ServerModule,
// 如果使用了 @angular/flext-layout,这里也需要引入服务端模块
FlexLayoutServerModule,
],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: UniversalRelativeInterceptor,
multi: true
}
],
bootstrap: [AppComponent],
})
export class AppServerModule { }
这样一来,任何针对相对地址的请求都会自动转换为绝对地址请求,在 SSR 场景下不再出问题。
经过上述步骤,如果通过 npm run build:ssr 构建项目,会发现 dist/ 下只有一个 index.html 文件。打开该文件,会发现其中仍有 这样的元素,页面内容并未直接在 HTML 中生成。这是因为 Angular 使用动态路由,例如 /product/:id 这种形式,页面渲染结果需要在执行 JS 之后才能确定。因此 Angular 使用 Express 作为 Web 服务器,在运行时根据用户请求(如爬虫请求),通过模板引擎动态生成静态 HTML 界面。
而 prerender(通过 npm run prerender)则会在构建时就生成静态 HTML 文件。例如做一个企业官网,页面数量不多,很适合用预渲染技术,把这几个页面的静态 HTML 文件提前生成好,避免运行时动态生成,从而进一步提升网页访问速度和用户体验。
需要进行预渲染的网页路径有几种提供方式:
1. 通过命令行的附加参数:
ng run:prerender --routes /product/1 /product/2
2. 如果路径较多,比如针对 product/:id 这种动态路径,可以准备一个路径文件:
routes.txt
/products/1 /products/23 /products/145 /products/555
然后在命令行参数中指定该文件:
ng run:prerender --routes-file routes.txt
3. 在项目的 angular.json 文件中直接配置路径:
"prerender": {
"builder": "@nguniversal/builders:prerender",
"options": {
"routes": [ // 这里配置
"/",
"/main/home",
"/main/service",
"/main/team",
"/main/contact"
]
},
配置完成后,重新执行预渲染命令(npm run prerender 或上述带命令行参数的方式)。编译完成后,再打开 dist/ 下的 index.html,会发现 消失了,取而代之的是主页的实际内容。同时也会生成相应的路径目录以及各个目录下的 index.html 子页面文件。
SEO 的关键在于网页的 title、keywords 和 description。对于希望被搜索引擎收录的页面,需要在代码中提供这些内容。
在 Angular 14 中,如果路由界面通过 Routes 配置,可以直接将网页的静态 title 写在路由配置里:
{ path: 'home', component: AbmHomeComponent, title: '<你想显示在浏览器 tab 上的标题>' },
此外,Angular 也提供了可注入的 Title 和 Meta 服务,用于动态修改网页标题和 meta 信息:
import { Meta, Title } from '@angular/platform-browser';
export class AbmHomeComponent implements OnInit {
constructor(
private _title: Title,
private _meta: Meta,
) { }
ngOnInit() {
this._title.setTitle('<此页的标题>');
this._meta.addTags([
{ name: 'keywords', content: '<此页的 keywords,以英文逗号隔开>' },
{ name: 'description', content: '<此页的描述>' }
]);
}
}
Angular 作为一款企业级 SPA 开发框架,在模块化组织和团队协作开发方面有着独特优势。发展到 v14 版本,更是提供了不依赖 NgModule 的独立 Component 功能,进一步简化了模块化架构。
Angular Universal 的核心目标是将 Angular 应用进行服务端渲染和生成静态 HTML。对于用户交互复杂的 SPA,并不推荐使用 SSR。但对于页面数量较少又有 SEO 需求的网站或系统,使用 Universal 和 SSR 技术无疑是一个明智的选择。
侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述