DEV Community

Majuran SIVAKUMAR
Majuran SIVAKUMAR

Posted on β€’ Edited on

10 2

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.

SurveyJS custom survey software

JavaScript UI Libraries for Surveys and Forms

SurveyJS lets you build a JSON-based form management system that integrates with any backend, giving you full control over your data and no user limits. Includes support for custom question types, skip logic, integrated CCS editor, PDF export, real-time analytics & more.

Learn more

πŸ‘‹ Kindness is contagious

Please leave a ❀️ or a friendly comment on this post if you found it helpful!

Okay