Search code examples
angularlinkedin-apisharingpage-title

How do i share title and metadata on LInkedin / FB of dynamically generated titles on sub-pages of Single page application?


I have set up a Single page application in Angular and currently dynamically set the page title and OG/ meta parameters based on the routing. i.e. for instance everytime the route is changed within the app using this. the titleService also updates the page title and meta tags of the page :

import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';

import { Component, OnInit } from '@angular/core';
import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
import { Title } from '@angular/platform-browser';

@Component({...})
export class AppComponent implements OnInit {
  constructor(
    private router: Router,
    private activatedRoute: ActivatedRoute,
    private titleService: Title
  ) {}
  ngOnInit() {
    this.router.events
      .filter((event) => event instanceof NavigationEnd)
      .map(() => this.activatedRoute)
      .map((route) => {
        while (route.firstChild) route = route.firstChild;
        return route;
      })
      .filter((route) => route.outlet === 'primary')
      .mergeMap((route) => route.data)
      .subscribe((event) => this.titleService.setTitle(event['title']));
  }
}

The problem now is that when I share one of the sub-pages of my app on Linkedin, It does not update the title and OG items of the URL based on my dynamically generated parameters within my app and instead shows the default title set on my app pre-routing.

How can I share to Linkedin / Facebook so that it shows the title and description based on the dynamically updated titles on my page?


Solution

  • Updating the meta data with a single page web app only updates it on the client side. LinkedIn, Facebook, and other social sharing sites do not load and interpret JavaScript. They simply grab the HTML file returned from the server and use whatever meta data is included there. As such, the custom routing and related data that has been implemented in JavaScript will never be read, and instead the default will always be shown.

    There are a number of possible solutions though, but all require a decent amount of work and have their pros and cons depending on the scope of your project. You could look into pre building the HTML on the server for initial page load or adjusting the HTML file's meta data on the server before sending it over.

    A simple pre-render approach:

    One approach to pre-rendering just the meta data would be to have a script that runs after you build your code that creates individual html pages with the adjusted meta data. (Disclaimer: I primarily use React and not much Angular, so the examples may be slightly off but the general idea should be the same across frameworks)

    Check out this file as an example -
    https://github.com/cid-harvard/growth-lab-app-front-end/blob/master/prerender-metadata.js

    Here I read in the built index.html file, modify the metadata for each route by using regex to find and replace a key set of characters (that are placed in the HTML template file) and then saved back out as a separate HTML file that is to be served with the proper meta data (but will otherwise run the SPA identically when the page is fully loaded).

    In order to be easily searched and replaced, the HTML template includes the "default" meta data like this:

    <title>$OG_TITLE</title>
    <meta name="description" content="$OG_DESCRIPTION" />
    

    I run the above script after the build script with a simple node prerender-metadata.js

    A simple server side render approach:

    Using the same style of building out the HTML template with searchable strings, you can also perform a similar search and replace on the server for more dynamic content. Instead of running a script right after the build step, you would run it on the server every time there is a request for the specific url. Here is an example that uses express and Node on the server:

    app.get('/terms-of-use', (req, res) => {
      const filePath = path.resolve(__dirname, '../../client', 'build', 'index.html');
      // read in the index.html file
      fs.readFile(filePath, 'utf8', function(err, data) {
        if (err) {
          return console.error(err);
        }
        // replace the special strings with server generated strings
        data = data.replace(/\$OG_TITLE/g, 'Terms of Use');
        const result  = data.replace(/\$OG_DESCRIPTION/g, "Terms of Use.");
        res.send(result);
      });
    });