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

Angular 18配置Zone.js兼容Supabase Promise的SSR超时问题

Angular 18 + Supabase SSR渲染超时问题解决方案

问题描述

在Angular 18项目中使用Supabase实现认证功能,编写了AuthService与登录组件,并为HttpClient启用withFetch()优化SSR,但访问/login页面时出现以下渲染超时错误:

[vite] Internal server error: Page /login did not render in 30 seconds.
      at Timeout.<anonymous> (C:\Users\kursp\Desktop\deluxe-detail\node_modules\@angular\build\src\utils\server-rendering\render-page.js:90:90)
      at Timeout.timer (C:\Users\kursp\Desktop\deluxe-detail\node_modules\zone.js\fesm2015\zone-node.js:2249:37)
      at _ZoneDelegate.invokeTask (C:\Users\kursp\Desktop\deluxe-detail\node_modules\zone.js\fesm2015\zone-node.js:398:33)
      at _ZoneImpl.runTask (C:\Users\kursp\Desktop\deluxe-detail\node_modules\zone.js\fesm2015\zone-node.js:158:47)
      at invokeTask (C:\Users\kursp\Desktop\deluxe-detail\node_modules\zone.js\fesm2015\zone-node.js:479:34)
      at Timeout.ZoneTask.invoke (C:\Users\kursp\Desktop\deluxe-detail\node_modules\zone.js\fesm2015\zone-node.js:468:48)
      at Timeout.data.args.<computed> (C:\Users\kursp\Desktop\deluxe-detail\node_modules\zone.js\fesm2015\zone-node.js:2231:32)
      at listOnTimeout (node:internal/timers:573:17)
      at process.processTimers (node:internal/timers:514:7)

推测为Zone.js未正确处理Supabase Promise导致任务阻塞。

相关代码

AuthService

import { Injectable } from '@angular/core';
import { AuthResponse, createClient } from '@supabase/supabase-js';
import { environment } from '../../environments/environment';
import { from, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  supabase = createClient(environment.supabaseUrl, environment.supabaseKey);

  register(email: string, password: string): Observable<AuthResponse> {
    const promise = this.supabase.auth.signUp({
      email,
      password
    })

    return from(promise);
  }

  login(email: string, password: string): Observable<AuthResponse> {
    const promise = this.supabase.auth.signInWithPassword({
      email,
      password
    });

    return from(promise);
  }
}

登录组件

import { Component, inject } from '@angular/core';
import { FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { AuthService } from '../auth/auth.service';

@Component({
  selector: 'app-login',
  standalone: true,
  imports: [ReactiveFormsModule],
  templateUrl: './login.component.html',
  styleUrl: './login.component.css'
})
export class LoginComponent {
  authService = inject(AuthService);
  router = inject(Router);

  errorMessage: string | null = null;

  contactForm = new FormGroup({
    email: new FormControl(''),
    password: new FormControl(''),
  });

  handleSubmit() {
    const rawForm = this.contactForm.getRawValue();
    if (rawForm.email && rawForm.password) {
      this.authService
        .login(rawForm.email, rawForm.password)
        .subscribe((result) => {
          if (result.error) {
            this.errorMessage = result.error.message;
          } else {
            this.router.navigateByUrl('/');
          }
        });
    }
  }
}

AppConfig配置

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideClientHydration(),
    provideHttpClient(withFetch()),
    importProvidersFrom(HttpClientModule)]
};

解决方案

1. 将Supabase Promise纳入Zone.js上下文

Supabase的原生Promise可能运行在Zone.js上下文之外,导致Angular无法跟踪异步任务进度,SSR时无法完成渲染。修改AuthService,引入NgZone并将Supabase调用包裹在zone.run()中:

import { Injectable, NgZone } from '@angular/core';
import { AuthResponse, createClient } from '@supabase/supabase-js';
import { environment } from '../../environments/environment';
import { from, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  supabase = createClient(environment.supabaseUrl, environment.supabaseKey);

  constructor(private zone: NgZone) {}

  register(email: string, password: string): Observable<AuthResponse> {
    const promise = this.zone.run(() => 
      this.supabase.auth.signUp({ email, password })
    );
    return from(promise);
  }

  login(email: string, password: string): Observable<AuthResponse> {
    const promise = this.zone.run(() => 
      this.supabase.auth.signInWithPassword({ email, password })
    );
    return from(promise);
  }
}

2. 限制认证操作仅在客户端执行

登录/注册属于客户端交互操作,不应在服务器端渲染时执行。使用isPlatformBrowser判断运行环境,确保操作只在浏览器中触发:

import { Component, inject, PLATFORM_ID } from '@angular/core';
import { FormGroup, FormControl, ReactiveFormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { AuthService } from '../auth/auth.service';
import { isPlatformBrowser } from '@angular/common';

@Component({
  selector: 'app-login',
  standalone: true,
  imports: [ReactiveFormsModule],
  templateUrl: './login.component.html',
  styleUrl: './login.component.css'
})
export class LoginComponent {
  authService = inject(AuthService);
  router = inject(Router);
  private platformId = inject(PLATFORM_ID);

  errorMessage: string | null = null;

  contactForm = new FormGroup({
    email: new FormControl(''),
    password: new FormControl(''),
  });

  handleSubmit() {
    if (!isPlatformBrowser(this.platformId)) return;

    const rawForm = this.contactForm.getRawValue();
    if (rawForm.email && rawForm.password) {
      this.authService
        .login(rawForm.email, rawForm.password)
        .subscribe((result) => {
          if (result.error) {
            this.errorMessage = result.error.message;
          } else {
            this.router.navigateByUrl('/');
          }
        });
    }
  }
}

3. 清理AppConfig冗余配置

provideHttpClient(withFetch())已包含HttpClient的全部功能,无需再导入HttpClientModule,避免配置冲突:

export const appConfig: ApplicationConfig = {
  providers: [
    provideZoneChangeDetection({ eventCoalescing: true }),
    provideRouter(routes),
    provideClientHydration(),
    provideHttpClient(withFetch())
  ]
};

原理说明

Zone.js是Angular用于跟踪异步操作的核心机制,只有在Zone上下文中的任务才能被Angular识别并触发变更检测。Supabase的Promise默认可能脱离Zone,导致SSR服务器无法感知任务完成,最终超时。同时,将认证操作限制在客户端,避免服务器端发起不必要的外部请求,进一步优化SSR渲染流程。

内容的提问来源于stack exchange,提问作者Haris Kurspahic

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 03:27:03