Angular Server Side Rendering (with hosting)

Written by © Maxence RABALLAND, 2021, My website.

Based on Angular documentation and Angular universal docs. More information about hosting and pricing on firebase.

Photo by Karl Pawlowicz on Unsplash

Prerequisites

You’ll have to create a firebase account first. Then install npm (find on node.js website) and Angular on your local machine :

Then, create your Firebase project. You have to enable firebase hosting and firebase functions. When you create your buckets, make sure to choose the right region (e.g., eu-west3, us-west1, …).

To enable firebase functions, you’ll have to switch to a firebase blaze plan. It’s the pay-as-you-go plan. For more info on pricing, visit the firebase pricing page.

Try this project in your environment.

Run the following commands on your command-line tool :

git clone https://github.com/maxencerb/Simple-ssr-Angular.git 
cd Simple-ssr-Angular
npm i

You can open your code editor code . and run the angular app locally with ng serve --open or run the dev server with npm run dev:ssr

Create the project

To start, use the angular CLI tools to create a simple project. I’m am using the latest version of angular and angular universal to date (Feb. 2021).

ng new my-app 
cd my-app
code .

This will create a simple angular project and open your favorite code editor. During development, you’ll be able to use the ng serve --open command to serve your app locally and test with hot reload.

Add angular universal

With the latest angular version, there is now a built-in angular command that adds the packages and remodels your folder structure.

ng add @nguniversal/express-engine

You’ll be prompted with the following :

console output for angular universal
console output for angular universal

So, this will create the following folder structure :

Now you can run your server locally with npm run dev:ssr.

Add to firebase project

First, create your firebase account and create your project. Then install the firebase CLI with npm and log in to your firebase account :

npm i -g firebase-tools 
firebase login

This will prompt you with a browser to sign in to your google account.

For CI (continuous integration), refer to the firebase docs and login with firebase login CI.

Now add firebase to your project :

This command will detect that you are on the angular universal project. Wen asked if true, say Yes.

Angular fire prompt for angular universal
Angular fire prompt for angular universal

Then choose the Firebase project you created for your app :

Angular firebase added output and choose project
Angular firebase added output and choose project

Once done, you’ll be all set.

Deploy

The last step is to deploy your app. Just run the command :

ng deploy

This will build your project for production and deploy to firebase functions.

Development Tips

Faster development

When using the server-side rendering local server, each time you modify your files, it will rebuild parts of the server. It can take a long time before it refreshes in your browser.

If you are modifying your angular app only, I recommend using the ng serve command. The hot reload is much faster.

Then, if you want to develop server features or debug your whole app, use npm run dev:ssr. It takes longer to refresh, so use it only if necessary.

Create a component, module, etc.

As you can see, in your src/app folder, there is now 2 app module : app.module.ts for your app and app.server.module.ts. When creating a component or a service, angular will want to auto-import to an app module. So in the CLI, you'll have to specify which one to choose. You might want to use app.module.ts in most cases.

To create a component, enter :

ng g c new-component --module app or ng g c new-component --module app.server

The same applies to modules and services.

Create a 404 Page

When you request a non-existing route while using server-side rendering, the server is trying to create an HTML template with a non-existing angular route and create a useless workload for your server.

Create a NotFoundComponent :

ng g c components/not-found --module app

Make sure your router-outlet markup is top-level on your app.component.html

<!-- app.component.html --><router-outlet></router-outlet>
<!-- not-found.component.html --><h1>404 - not found</h1>

Feel free to add your own content and styling. Now add this to your app-routing.module.ts :

// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';import { NotFoundComponent } from './components/not-found/not-found.component';const routes: Routes = [ // your routes here {path: '404', component:NotFoundComponent}, {path: '**', redirectTo:'/404'}];@NgModule({imports: [RouterModule.forRoot(routes, {initialNavigation: 'enabled'})],exports: [RouterModule]})export class AppRoutingModule { }

It is important to add your custom routes before these because they will search for the first matching pattern.

Custom express.js API endpoints

You can make modifications to your server settings in the server.ts file. To add custom API endpoints, add them before the built-in endpoints. This will overwrite them.

// server.ts...export function app(): express.Express {const server = express();const distFolder = join(process.cwd(), 'dist/<your-project>/browser');const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';server.engine('html', ngExpressEngine({bootstrap: AppServerModule,}));server.set('view engine', 'html');server.set('views', distFolder);// Add custom endpoints hereserver.get('/api/hello', (req, res) => {  // response example  res.send(`hello from maxence, the current time is ${Date.now()}`);});// End custom endpoints// built-in endpointsserver.get('*.*', express.static(distFolder, {maxAge: '1y'}));// All regular routes use the Universal engineserver.get('*', (req, res) => {res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });});return server;}...

View project on http://github.com.

Third year engineering student at ESILV, Paris