You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular Universal SSR 首次页面加载两次问题如何解决?

Angular Universal SSR页面重复加载问题解决方案

根因说明

你遇到的两种表现本质是Angular SSR水合(Hydration)的默认行为导致:

  • 无*ngIf="isBrowser"限制时,服务端返回完整渲染的HTML,客户端Angular实例启动后,默认会销毁服务端生成的DOM,重新渲染整棵组件树,因此出现两次加载的视觉效果
  • 加了*ngIf="isBrowser"限制后,服务端不会渲染该部分内容,SSR能力失效,自然页面源码看不到对应结构

具体解决步骤

1. 启用官方水合特性(Angular 16+ 优先使用)

Angular 16及以上版本内置了稳定的水合能力,开启后客户端会直接复用服务端生成的DOM,不会整树销毁重绘,彻底解决重复加载问题,配置方式如下:

// 独立API模式下在app.config.ts中配置
import { provideClientHydration } from '@angular/platform-browser';
import { ApplicationConfig } from '@angular/core';

export const appConfig: ApplicationConfig = {
  providers: [
    // 其他已有provider
    provideClientHydration()
  ]
};

// NgModule模式下在AppModule中配置
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

@NgModule({
  imports: [
    // 确保服务端和客户端的appId完全一致
    BrowserModule.withServerTransition({ appId: 'your-app-unique-id' }),
    // 其他已有import
  ]
})
export class AppModule {}

2. 移除不必要的isBrowser判断

删掉router-outlet外层的*ngIf="this.isBrowser"判断,保证服务端可以正常渲染该部分内容,页面源码就能看到完整结构。

3. 修复服务端/客户端逻辑不一致的问题

很多时候重复加载是因为服务端和客户端渲染出来的DOM结构不一致,Angular检测到差异后强制重绘导致,需要做以下检查:

  • 所有调用浏览器专属API(window、document、localStorage等)的代码,都要包裹在平台判断逻辑中:
    import { PLATFORM_ID, Inject } from '@angular/core';
    import { isPlatformBrowser } from '@angular/common';
    
    constructor(@Inject(PLATFORM_ID) private platformId: object) {
      if (isPlatformBrowser(this.platformId)) {
        // 仅在客户端执行的逻辑,比如操作window、绑定滚动事件等
      }
    }
    
  • 检查onActivate方法中有没有强制触发全局变更检测、路由重定向这类会导致整树重绘的逻辑,调整为按需执行。

4. 低版本Angular(16以下)额外配置数据缓存

低版本没有内置水合能力时,大概率的重复加载是因为服务端请求了一次数据渲染页面,客户端启动后又重复请求了一次数据,页面内容刷新导致视觉上的两次加载,用TransferState缓存服务端请求的数据即可:

// 数据请求服务示例
import { TransferState, makeStateKey } from '@angular/platform-browser';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { PLATFORM_ID, Inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { of } from 'rxjs';
import { tap } from 'rxjs/operators';

const HOME_DATA_KEY = makeStateKey('home-page-data');

@Injectable({ providedIn: 'root' })
export class HomeDataService {
  constructor(
    private http: HttpClient,
    private transferState: TransferState,
    @Inject(PLATFORM_ID) private platformId: object
  ) {}

  getHomeData() {
    // 客户端优先读取服务端缓存的 data,不重复发请求
    if (isPlatformBrowser(this.platformId) && this.transferState.hasKey(HOME_DATA_KEY)) {
      const data = this.transferState.get(HOME_DATA_KEY, null);
      this.transferState.remove(HOME_DATA_KEY);
      return of(data);
    }
    // 发起请求获取数据
    return this.http.get('/api/home-page-data').pipe(
      tap(data => {
        // 服务端将请求到的数据存入缓存,随HTML返回给客户端
        if (isPlatformServer(this.platformId)) {
          this.transferState.set(HOME_DATA_KEY, data);
        }
      })
    );
  }
}

同时需要分别在AppModule中导入BrowserTransferStateModule,服务端模块中导入ServerTransferStateModule。

内容的提问来源于stack exchange,提问作者Vichakshan Technologies

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 20:15:03