DEV Community

Majuran SIVAKUMAR
Majuran SIVAKUMAR

Posted on • Edited on

Prerender an Angular app

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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) });
  }
}


Enter fullscreen mode Exit fullscreen mode

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 {}
Enter fullscreen mode Exit fullscreen mode

So now when you run your project with npm start, you should have 3 pages

3. Install Angular Universal

Now that we have our project configured, we can move on and install Angular Universal.

ng add @nguniversal/express-engine
Enter fullscreen mode Exit fullscreen mode

If you open your package.json, you should find a new script :

"prerender": "ng run angular-prerender-example:prerender"
Enter fullscreen mode Exit fullscreen mode

4. Static routes

To prerender static routes, it's pretty straightforward, run :

npm run prerender
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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>
...

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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": {}
  }
}
...
Enter fullscreen mode Exit fullscreen mode

Then run :

npm run prerender
Enter fullscreen mode Exit fullscreen mode

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 # πŸ‘ˆ 
Enter fullscreen mode Exit fullscreen mode

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>
...
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
ionellupu profile image
Ionel Cristian Lupu

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?

Collapse
 
maj07 profile image
Majuran SIVAKUMAR

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.

Collapse
 
raulmar profile image
RaΓΊl MartΓ­n

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.