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
相关产品推荐
相关产品推荐

