Search code examples
angularsocket.ioangular-universal

socketio-client causing "Internal server error: Page / did not render in 30 seconds" in Angular 18


I'm working on an Angular application where I use socketio-client within a SocketService. However, I encounter the following error when attempting to use the socket connection in the service constructor:

01:05:28 [vite] Internal server error: Page / did not render in 30 seconds.
      at Timeout.<anonymous> (C:\Users\azoul\OneDrive\Bureau\code\dm-front\node_modules\@angular\build\src\utils\server-rendering\render-page.js:90:90)
      at Timeout.timer (C:\Users\azoul\OneDrive\Bureau\code\dm-front\node_modules\zone.js\fesm2015\zone-node.js:2249:37)
      at _ZoneDelegate.invokeTask (C:\Users\azoul\OneDrive\Bureau\code\dm-front\node_modules\zone.js\fesm2015\zone-node.js:398:33)
      at _ZoneImpl.runTask (C:\Users\azoul\OneDrive\Bureau\code\dm-front\node_modules\zone.js\fesm2015\zone-node.js:158:47)
      at invokeTask (C:\Users\azoul\OneDrive\Bureau\code\dm-front\node_modules\zone.js\fesm2015\zone-node.js:479:34)
      at Timeout.ZoneTask.invoke (C:\Users\azoul\OneDrive\Bureau\code\dm-front\node_modules\zone.js\fesm2015\zone-node.js:468:48)
      at Timeout.data.args.<computed> (C:\Users\azoul\OneDrive\Bureau\code\dm-front\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)

socket.service.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { io, Socket } from 'socket.io-client';

@Injectable({
  providedIn: 'root'
})
export class SocketService {

  private socket!: Socket;

  constructor() {
    this.socket = io('http://localhost:3000');
  }
}

home.component.ts

import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import { SocketService } from '../../core/services/socket.service';
import { io, Socket } from 'socket.io-client';

@Component({
  selector: 'app-home',
  standalone: true,
  imports: [
    RouterLink // I use standalone components
  ],
  templateUrl: './home.component.html',
  styleUrl: './home.component.scss'
})
export class HomeComponent {
  
  constructor(
    private socketService: SocketService
  ) {}
}

Solution

  • Seems that io function automatically connects the client to the given server (http://localhost:3000). So when Angular is initializing the app, you will connect to the websocket. But you won't ever disconnect as you haven't called socket.disconnect() (or socket.close). This means Angular's Zone.js will probably have some pending tasks related to the socket connection and therefore won't ever consider the app to be stable. And Angular needs the app to be stable (without any pending tasks) to be considered initialized.

    To solve that, you can start the socket once Angular has finished initializing the application. To do so, you can connect the socket once the app is stable

    This way, the app will init as usual. Then, when stable, the connection to the websocket will happen with a permanent connection that will keep the app non stable. But that's fine at that point that everything's booted. And makes sense, given data is permanently incoming from the websocket until you close it.

    For instance, this could be the updated socket service:

    import { ApplicationRef, inject, Injectable } from '@angular/core';
    import { first, Observable } from 'rxjs';
    import { io, Socket } from 'socket.io-client';
    
    
    @Injectable({
      providedIn: 'root'
    })
    export class service {
      private socket: Socket;
    
      constructor() {
        this.socket = io('http://localhost:3000', { autoConnect: false });
        inject(ApplicationRef).isStable.pipe(
          first((isStable) => isStable))
        .subscribe(() => { this.socket.connect() });
      }
    }