Skip to content

SSR pitfalls #571

@TheDelta

Description

@TheDelta

Sorry to hijack this issue ticket but for those who would like to have better SSR support, here is some code.

Requirements: Angular SSR with server.ts (running ng18).

In server.ts, update the providers and inject the req to the application:

import { REQUEST } from 'src/app/tokens/express.token';

  server.get('*', (req, res, next) => {
    commonEngine
      .render({
        providers: [
          { provide: APP_BASE_HREF, useValue: baseUrl },
          { provide: REQUEST, useValue: req },
        ],
      })

the express.token.ts is:

import { InjectionToken } from '@angular/core';
import { Request, Response } from 'express';

export const REQUEST = new InjectionToken<Request>('REQUEST');
export const RESPONSE = new InjectionToken<Response>('RESPONSE');

Now, when we serve via the server, we inject the request and we can use this request application wide through the injection token ;)

We must create a custom hook now:

import { isPlatformServer } from '@angular/common';
import { Inject, Injectable, Optional } from '@angular/core';
import { Attributes, IntersectionObserverHooks } from 'ng-lazyload-image';
import { REQUEST } from '../tokens/express.token';
import type { Request } from 'express';

@Injectable()
export class LazyLoadImageHooks extends IntersectionObserverHooks {
  constructor(@Optional() @Inject(REQUEST) readonly request: Request | null) {
    super();
  }

  override isBot(attributes?: Attributes): boolean {
    // on server, try to get the user agent and patch the navigator with it if found
    if (isPlatformServer(this.platformId)) {
      const userAgent = this.request?.headers['user-agent'];
      if (userAgent) {
        this.navigator = <Navigator>{
          userAgent,
        };
      }
    }

    return super.isBot(attributes);
  }
}

NOTE: I ran into the issue, that I thought I have to provide this in app.modules and it would be global. Unfortunatly the token is per module, so you either have to provide the hook in each module where you import the lazy load module OR you create a shared / lazyload module and import, export the lazy load module and provide the hook there. Then you only need to define the hook once and you simply import now this new lazyload module instead of the library one.

(and of course, if your webserver / express server does a bot check already, you could also change the code and get that custom header / variable)

Willing to do a PR if I find some time and discuss if this can be improved, etc. - right now I just want to make my personal website better with SSR.

Bonus: If you want to have the defaultImage / SVG preloaded already (so it's serverd directly and looks nicer) you can do the following overrides to the custom hook:

  override setup(attributes: Attributes): void {
    // on server, we must skip the IntersectionObserver creation
    if (isPlatformServer(this.platformId)) {
      attributes.customObservable = of({ isIntersecting: false });
    }
    super.setup(attributes);
  }

  override isDisabled(): boolean {
    // on server, ensure it's not disabled
    return !isPlatformServer(this.platformId) && super.isDisabled();
  }

This simply allows the execution of the logic but won't show the actual image, but will set already the defaultImage / SVG.
I personally prefer this, even better would be directly embedding the svg and then removing it.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions