We have an angular app that renders Product descriptions. It has two routes defined like this:
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: ':product', component: ProductComponent }
];
Approach
Project
Create create a new Angular project with routing, skip test generation, and open it up in VSCode:
ng new ngscully --routing --skip-tests
cd ngscully
code .
Components
Create home
and product
component:
ng g c components/product
ng g c components/home
Update the ProductComponent
such that it shows the current :product
routed to. Note that we have moved the component template inside the component.
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-product',
template: `<p>The product id is {{product}}</p>`,
styleUrls: ['./product.component.scss']
})
export class ProductComponent implements OnInit {
public product: string
constructor(private route:ActivatedRoute) {
this.product = this.route.snapshot.paramMap.get(':product');
}
ngOnInit(): void {
}
}
Routing
Replace everything in src/app/app.component.html
with
<router-outlet></router-outlet> .
Update src/app/app-routing.module.ts
with the routes:
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: ':product', component: ProductComponent }
];
Serve the application just to make sure we are all good so far:
ng serve -o
Test the routes:
http://localhost:4200/
http://localhost:4200/someblogid
The :product
route should show someblogid
rendered when that route is triggered.
Stop the server as we need to compile the build with Scully added.
Adding Scully without stopping the live server causes the live server build to fail.
Add Scully
ng add @scullyio/init
And build and prerender our app.
ng build
npm run scully
Scully Rendered Routes
Look in src/assets/scully-routets.json
.
We see that Scully rendered this route:
[{"route":"/"}]
Which is the HomeComponent
.
The corresponding static asset is dist/static/index.html
.
Looking inside it we see that is now includes home works!
and that is content normally generated by Javascript after application load.
But what about the product route? We did not tell Scully the paths of any of the dynamic :product
id values that this route can take and in order to render these dynamic routes we must do so.
Open scully.ngscully.config.ts
(Located in the root project folder) and addextraRoutes: [‘/p1’, ‘/p2’]
so that it looks like this:
import { ScullyConfig } from '@scullyio/scully';
export const config: ScullyConfig = {
projectRoot: "./src",
projectName: "ngscully",
outDir: './dist/static',
routes: {},
extraRoutes: ['/p1', '/p2']
};
Now run npm run scully
again.
Scully tells us what files it is generating:
Route "" rendered into file: "./dist/static/index.html"
Route "/p2" rendered into file: "./dist/static/p2/index.html"
Route "/p1" rendered into file: "./dist/static/p1/index.html"
And if we look in dist/static/p2/index.html
we see that it contains the paragraph string product id is p2
.
Thus we now have static content for our SEO needs. But what about dynamic title
and meta
tag updates?
Title and Meta Tag Service
Create an SEOService
service.
ng g s services/SEO
This is the implementation:
import { Injectable } from '@angular/core';
import { Meta, Title } from '@angular/platform-browser';
@Injectable({
providedIn: 'root'
})
export class SEOService {
constructor(private titleService: Title, private metaService: Meta) { }
public setTitle(title: string) {
this.titleService.setTitle(title)
}
public updateMeta(description: string) {
this.metaService.updateTag({ name: 'description', content: `Product Page for ${description}` });
}
}
Refactor the Product Component
Update the product component design to such that it sets title
and meta
tags dynamically:
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { SEOService } from 'src/app/services/seo.service';
@Component({
selector: 'app-product',
template: `<p>The product id is {{id}}</p>`,
styleUrls: ['./product.component.scss']
})
export class ProductComponent implements OnInit {
public id: string
constructor(private route: ActivatedRoute, private seo: SEOService) {
this.route.paramMap.subscribe(params => {
this.id = params.get("product")
});
}
ngOnInit(): void {
this.seo.setTitle(this.id)
this.seo.setMeta(this.id)
}
}
Rerun the Scully Build
ng build
npm run scully
Take a look at the static pages generated for the product routes. They now include the dynamic title and meta tags we added using the SEOService
.
Note that if you refactor the app it is very important to run ng build
again as Scully uses the build to create the static files.
A simple way to ensure this is to update the package.json
Scully script so that it looks like this:
"scully": "ng build --prod && scully",