Hello π ! Not so long ago, I meet the need to prerender an Angular application and I taught it will be nice to share it with you.
Let's check step by step, how to create and prerender a new Angular app.
If you are interested to prerender an existing app, You can jump to step 3. π
FYI: You can also clone this example on Github π
1. New project
Let's create a new angular project with Angular Cli
ng new angular-prerender-test
2. Create some routes
For the example I will create 3 routes :
-
/
: Home page (static route) -
/contact
: Contact page (static route) -
/user/:id
: User profile (dynamic route), content will be different for each id
You can create your components via Angular Cli with :
ng g c YourComponentName
Here is what my components looks like:
// home.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-home',
template: `<h1>Home Page</h1>
<p>Hello World, welcome to the home page</p> `,
styles: [],
})
export class HomeComponent{
constructor() {}
}
// contact.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-contact',
template: `<h1>Contact</h1>
<p>You can contact me on : +1 *** *** *** *23</p>`,
styles: [],
})
export class ContactComponent {
constructor() {}
}
// user.component.ts
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-user',
template: `<h1>User: {{ id }}</h1>
<p>This is {{ id }}'s profile</p>`, // π user id in template
styles: [],
})
export class UserComponent {
id = '';
constructor(private route: ActivatedRoute) {
// Get param from route
this.route.params.subscribe({ next: (res) => (this.id = res.id) });
}
}
and your app-routing.module.ts
should be like :
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ContactComponent } from './contact/contact.component';
import { HomeComponent } from './home/home.component';
import { UserComponent } from './user/user.component';
const routes: Routes = [
/* Home page */
{
path: '',
component: HomeComponent,
},
/* Contact page */
{
path: 'contact',
component: ContactComponent,
},
/* User profile page */
{
path: 'user/:id',
component: UserComponent,
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
So now when you run your project with npm start
, you should have 3 pages
- http://localhost:4200 => home page
- http://localhost:4200/contact => Contact page
- http://localhost:4200/user/bob => bob's profile page
3. Install Angular Universal
Now that we have our project configured, we can move on and install Angular Universal.
ng add @nguniversal/express-engine
If you open your package.json
, you should find a new script :
"prerender": "ng run angular-prerender-example:prerender"
4. Static routes
To prerender static routes, it's pretty straightforward, run :
npm run prerender
If you check the build, you should have something like :
dist/angular-prerender-example/browser
βββ 3rdpartylicenses.txt
βββ contact
β βββ index.html # π contact page
βββ favicon.ico
βββ index.html # π home page
βββ index.original.html
βββ main.271dcd2770e618160ca0.js
βββ polyfills.bf99d438b005d57b2b31.js
βββ runtime.359d5ee4682f20e936e9.js
βββ styles.617af1cc16b34118b1d3.css
If you open those file you are going to have :
<!-- index.html -->
...
<div _ngcontent-sc36="" class="container">
<router-outlet _ngcontent-sc36=""></router-outlet>
<app-home>
<h1>Home Page</h1>
<p>Hello World, welcome to the home page</p>
</app-home>
</div>
...
<!-- contact/index.html -->
...
<div _ngcontent-sc36="" class="container">
<router-outlet _ngcontent-sc36=""></router-outlet>
<app-contact>
<h1>Contact</h1>
<p>You can contact me on : +1 *** *** *** *23</p>
</app-contact>
</div>
...
Tada ! Our static routes are prerendered ! π
But, wait what about my dynamic route /user/:id
?!? π€
5. Dynamic routes
For dynamic routes, we should define which routes we want to prerender. For it, we need to create a new file user-routes
at the root of the project and list all routes you want.
You can name the file as you like
Example :
/user/Joan
/user/Sherry
/user/Frank
/user/Bob
Let's open angular.json
.
In prerender
section add a new attribute routesFile
with your file name.
...
"prerender": {
"builder": "@nguniversal/builders:prerender",
"options": {
"browserTarget": "angular-prerender-example:build:production",
"serverTarget": "angular-prerender-example:server:production",
"routes": [
"/"
],
"routesFile" : "user-routes" // π add your file name
},
"configurations": {
"production": {}
}
}
...
Then run :
npm run prerender
Let's check the output:
dist/angular-prerender-example/browser
βββ 3rdpartylicenses.txt
βββ contact
β βββ index.html
βββ favicon.ico
βββ index.html
βββ index.original.html
βββ main.271dcd2770e618160ca0.js
βββ polyfills.bf99d438b005d57b2b31.js
βββ runtime.359d5ee4682f20e936e9.js
βββ styles.617af1cc16b34118b1d3.css
βββ user
βββ Bob
β βββ index.html # π
βββ Frank
β βββ index.html # π
βββ Joan
β βββ index.html # π
βββ Sherry
βββ index.html # π
Let's open one of those files :
<!-- user/bob/index.html -->
...
<div _ngcontent-sc36="" class="container">
<router-outlet _ngcontent-sc36=""></router-outlet>
<app-user>
<h1>User: Bob</h1>
<p>This is Bob's profile</p>
</app-user>
</div>
...
and that's it, routes listed in user-routes
are prerendered ! π
Hope it helped some of you.
Thanks for reading. π
Source code available on Github π
Top comments (3)
99% of the time you won't have the
user-routes
file written manually. The routes in there should be automatically generated using some data from the database. How would you approach this?Hello @ionellupu π
Yes the route file was written manually for the purpose of this article.
I agree often you will have to generate this file from an external source like Database, CMS / API etc.
For example if we take product details pages for a E-commerce, we can get the product ids we need (ex: most viewed) from an api or script that generate the routes file locally or on a cdn. Then we can fetch this file before running our prerender script.
Its a suggestion, but of course it will depends on your over all architecture and needs.
Hi @maj07, I understand the approach you propose. However, I see Angular Universal pre-render as a way to run my CI pipe in order to generate all the html and then distribute the static pages on a CDN. So I think it's a bit overkill to have to run a server just to run a script for fetching the routes.