简体   繁体   中英

Setting page meta tags with Angular universal dev:ssr works but build:ssr is not working

I am having a challenge with building and serving an Angular Universal application with the express server for good SEO. When I serve it with the script with command "dev:ssr": "ng run motif:serve-ssr" and access it on the browser on the default port 4200 and view the source using the Chrome's view page source option, I can see the correct dynamic meta tags and also the HTML source, which works perfect.

But when I build and serve it with the below commands, the meta tags are not being updated

"build:ssr": "ng build --prod && ng run motif:server:production"

npm run build:ssr && npm run serve:ssr

It builds and renders with no errors, but when I view the page source on Chrome I see only the meta tags set for the index.html file but I am expecting it to set the tags like the page title, description, image etc for each article data.

I am setting the meta tags in a router data resolver service, and it works fine because I can see its works when running the dev:ssr script.

The CLI version is 9.1.12 (v10 & 11v having issues with ssr and the window object). Am I missing something or doing something wrong?

I'd appreciate any suggestions and solutions to this issue.

Update:

A simplified version of the data resolver is below:

import { Injectable } from '@angular/core';
import { AngularFirestore } from '@angular/fire/firestore';
import { DomSanitizer, TransferState } from '@angular/platform-browser';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { SeoSocialShareData, SeoSocialShareService } from 'ngx-seo';
import { Observable, of, Subscription } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class ArticleDataResolver implements Resolve<any> {
  constructor(
    private seoSocialShareService: SeoSocialShareService,
    private angularFirestore: AngularFirestore,
  ) { }

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<any> | Promise<any> | any {
    const stateKey = state.url;

    const ref = this.angularFirestore.collection("movementArticles");

    return ref
      .doc(route.params.id)
      .get()
      .pipe(
        map((dataSnap) => {
          const seoData: SeoSocialShareData = {
            title: '...',
            description: '...',
            image: '...',
            author: '...',
            keywords: `...`,
            url: `...`,
            published: '...',
          };

          this.seoSocialShareService.setData(seoData);

          return dataSnap;
        })
      );
  }
}
 

I share this service with you You Can omit the translate service if you do not need translations

and try call the methods in the constructor or ngOnInit of you component view

import { Injectable } from '@angular/core';
import { Title, Meta } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class SEOService {
  constructor(private title: Title, private meta: Meta, private translate: TranslateService) {}

  async getTranslation(word: string) {
    if (word) {
      return await this.translate
        .get(word)
        .toPromise()
        .then(resp => {
          return resp;
        });
    } else {
      return '';
    }
  }

  async updateTitle(title: string) {
    const translation = await this.getTranslation(title);
    this.title.setTitle(translation);
  }

  async updateDescription(desc: string) {
    const translation = await this.getTranslation(desc);
    this.meta.updateTag({ name: 'description', content: translation });
  }

  async updateKeywords(keywords: string) {
    const translation = await this.getTranslation(keywords);
    this.meta.updateTag({ name: 'keywords', content: translation });
  }

  async updateOgUrl(url: string) {
    const translation = await this.getTranslation(url);
    this.meta.updateTag({
      name: 'og:url',
      property: 'og:url',
      content: translation
    });
  }

  async updateOgTitle(ogTitle: string) {
    const translation = await this.getTranslation(ogTitle);
    this.meta.updateTag({
      name: 'og:title',
      property: 'og:title',
      content: translation
    });
  }

  async updateOgDescription(ogDesc: string) {
    const translation = await this.getTranslation(ogDesc);
    this.meta.updateTag({
      name: 'og:description',
      property: 'og:description',
      content: translation
    });
  }

  async updateOgImage(ogImg: string) {
    const translation = await this.getTranslation(ogImg);
    this.meta.updateTag({
      name: 'og:image',
      property: 'og:image',
      content: translation
    });
  }

  async disableFollow() {
    this.meta.addTag({
      name: 'robots',
      property: 'robots',
      content: 'noindex, nofollow'
    });
  }

  async enableFollow() {
    this.meta.removeTag('robots');
  }
}

Example for a global ngOnInit


import { Component, OnInit } from '@angular/core';
import {
  Router,
  NavigationEnd,
  ActivatedRoute,
  RouterEvent
} from '@angular/router';

export class AppComponent implements OnInit {

  constructor(
    private router: Router,
    private route: ActivatedRoute,
    private seoService: SEOService,
  ) {
   
  }

ngOnInit() {
    this.router.events
      .pipe(
        filter(event => event instanceof NavigationEnd),
        map(() => this.route),
        map(route => {
          while (route.firstChild) {
            route = route.firstChild;
          }
          return route;
        }),
        filter(route => route.outlet === 'primary'),
        mergeMap(route => route.data)
      )
      .subscribe(event => {
        this.seoService.updateTitle(event['title']);
        this.seoService.updateDescription(event['description']);
        this.seoService.updateKeywords(event['keywords']);
        this.seoService.updateOgUrl(event['url']);
        this.seoService.updateOgTitle(event['ogTitle']);
        this.seoService.updateOgDescription(event['ogDesc']);
        this.seoService.updateOgImage(event['ogImage']);
      });
  }
}

Hope this can help you;)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM