Angular pre-rendering: Scully.io vs Angular Universal

Jasper Fioole
Jool Software Professionals
8 min readMar 18, 2020

--

In some cases you want your angular app to be pre-rendered. Pre-rendering your app can hugely improve the performance of your app. It doesn’t require any client side rendering on the users computer because it is already rendered. If all html of your app is pre-rendered it doesn’t require javascript to show the html. Robots/crawlers don’t use javascript so pre-rendering your app will highly optimize your app for search engines such as Google.

This can be achieved in a few ways, in this article I’m going to show you how to do this using Angular Universal (SSR) and Scully.io. Afterwards we can compare both techniques and tell them apart.

Introduction to Angular Universal
You might be familiar with Server Side Rendering(SSR) using Angular Universal, but in some cases you’d like your app to pre-render some pages. In this post, I’m going to explain how to use Angular Universal and Angular 9 for pre-rendering.

You can find the Angular Universal documentation over here.

Introduction to Scully.io
Since a few months a committed group of people started working on Scully.io, the open source project is available for everyone to use. The past month I worked closely with the Scully team to provide feedback to improve the library. I’m going to show you how to use Scully.io for your project.

Scully.io relies on the Angular build process, you can only run Scully after your app was built by angular. Scully will serve the app that was built by Angular and will convert this to a pre-rendered version of the app.

You can find the Scully.io documentation over here.

Creating an angular app

This step is equal for Angular Universal Pre-rendering and Scully.io. If you’d like to add pre-rendering to an existing app you can skip this section. Before starting make sure you are using the right angular version. You can check this using the following command.

ng --version

Angular Universal Pre-rendering requires Angular 9+
Scully.io requires Angular 8+

In my case I’m using Angular version 9.0.4.
Now lets start by creating a new app using the Angular CLI, you can name your app whatever you like, I’ll stick with static-app .

ng new static-app

Make sure routing is enabled during the creation of the app.

Adding content for testing

If you are working on an existing app you can skip this part.

Before pre-rendering the app we can create some pages to see how it works. Let’s start by adding a simple folder structure.

cd src/app/ && mkdir components && cd components/

Now we can create some components which we can use to test the pre-rendering. In this example I’ll generate two components using CLI.

ng generate component page-one --skip-import

And the second one.

ng g c page-two --skip-import

Note: when generating the components we need to add --skip-import to the command. Because we are using these components as pages, we will declare those inside the router. If needed you can add them to a module later on.

After generating our pages we have to add the components in the routes file. In our project we can update the app-routing.module.ts to make our pages accessible.

Update the AppComponent

Simply replace the content of app.component.html with the html listed below. This will make it easier to see if everything works.

Our example app is just for demonstration purposes so we’ll will keep the styling default.

Angular Universal (SSR) Pre-rendering

Now we can add Angular Universal to an existing app, this can be the app we’ve created above or another app.

To start using server side rendering we have to add the Angular Universal schematics. We can do this by executing an CLI command.

ng add @nguniversal/express-engine

Adding the schematics updates the angular.json as well, we can use this later on to define which pages we want to have pre-rendered.

Defining what to pre-render

As stated before, Angular Universal added a few things to our angular.json as well. Open the file and look for the prerender section and update the routes array to your liking. If you followed the example in the previous part of this article you can add the one and two routes here.

Dealing with dynamic routes

If your app does not include dynamic routes you can skip this part.

Some apps use routes with parameters, the guess parser is unable to detect the dynamic routes automatically, so how do we solve this. Angular Universal provides two solutions for this, we could append all undetected routes to our CLI command. This is not optimal, but there is an alternative. We could create a routes file and pass it to the pre-rendering process (we’ll look into this later on in this article).

guess-parser contains a collection of parsers which can statically analyze our Angular application to discover how the routes from the analytics source map to JavaScript bundles.

Updating our example
Let’s add an additional component to our example.

ng generate component page-three --skip-import

After creating our component we need to update the routes of our app to the following (don’t forget to update the imports).

In our example it wasn’t necessary to add our previous pages to a module (because we didn’t use anything from another module), in PageThreeComponent we would like to use a few things from other modules so make sure you add it to the declarations section in the app.module.ts .

We’d like to use the CommonModule so make sure it is imported in the app.module.ts as well.

First lets update the code of our PageThreeComponent , we’ll start with the page-three.component.ts file. We’ll keep it pretty simple, just to demonstrate that everything is working fine.

Define an observable which contains route param events

Now let’s update our app-three.component.html file.

We’ll just display our param output as json

At last update the app.component.html with some additional menu items to make it easier to browse trough our app pages.

Creating the routes file

Create a file called routes.txt in the root folder of your project, fill this file with all dynamic routes you want to have pre-rendered. It is not necessary to add static routes because angular already detected those.

/three/1
/three/2

Now we need to update the prerender command in our package.json to use the newly created route file.

"prerender": "ng run static-app:prerender --routesFile routes.txt"

TIP: create a (node) script to create and fill the routes.txt file automatically

Pre-render the app

Use the following npm command to prerender the app.

npm run prerender

Just like after building an angular app we are able to find the generated files in the dist folder. Now lets see if everything works.

If you don’t have a http-server installed on your computer you could simply install the http-server npm library for that. Simply run the following command (add -g to install http-server globally).

npm i http-server

If you installed the http-server inside your project, you can run the following command using npx. If you installed http-server globally just execute it without using npx.

npx http-server dist/static-app/browser

TIP: view the page source of your app. If everything worked out so far you’ve successfully pre-rendered your app! When a crawler/bot views your app, it won’t use javascript, simply view the page source in your browser to see what is pre-rendered.

Creating a Scully app

Let’s add Scully to an existing app(this can be any angular app using Angular 8+), navigate to the root directory and run the following command.

ng add @scullyio/init

When using Scully you can choose to use guess-parser to let Scully scan for static routes. This can be achived by adding --scanRoutes to the parser command, this command is shown in the “Pre-render the app” section.

Handling dynamic routes

If your app does not use any dynamic routes, you can skip this section.

Scully created a config file in the root folder of our project called scully.<project_name>.config.js , we can use this file to define our dynamic routes.

When editing this file we can add extraRoutes to the config object to define our routes. In our test project the config file would look something like this.

Pre-render the app

To pre-render the app we have to run two command, we’ll start by building our angular app.

ng build

When our app is built we can run the Scully parser with the following command. We add --scanRoutes to the CLI command to use the guess-parser to automatically detect our static routes, we added our dynamic routes in the previous section.

npm run scully -- --scanRoutes

The newest version of Scully includes an http-server, after running the command above we can simply open the url listed in the command output. If you’d like to close the server simply type q and hit enter.

By default Scully adds the pre-rendered site to the static folder inside the dist folder.

Scully.io VS Angular Universal

Now you know how to pre-render you angular app, well done!

So lets find out which one to use!

Scully.io

Setting up a project with Scully is pretty easy, when your app doesn’t use any dynamic routes it is as simple as adding Scully to your project. Adding dynamic routes to your app is pretty simple as well.

A big advantage of using Scully is the possibility to use json/markdown files as page content. These routes can be detected automatically.

Scully is still in development (currently Alpha), so it can be buggy. I’ve experienced this a few times during a development project. Some issues might keep you from using Scully in production. Right now when the Scully pre-rendering fails the command will not return an exit code, this means that CI/CD will not detect that the process has failed and deploys a broken app.

Angular Universal

Angular Universal isn’t new but it got a lot easier. Since Angular 9 we have the possibility to pre-render our pages which is not very difficult.

A small downside is that we have to define each route that we want to have pre-rendered. This may not be a downside in each project, in some cases you only want to pre-render a few pages, this would make that possible.

Angular Universal is provided by the official Angular team, because of this and the fact that it was tested by the team and the community as well reduces the chance of bugs.

Conclusion

The setup for both techniques is pretty simple. Angular Universal requires a bit more setting then Scully.io but this is still manageable.

You’re probably wondering what technique you should be using for your application. Well, it depends, both techniques works fine. Because Scully is still in a development stage it can be buggy at times. The server that Scully uses to pre-render the website will sometimes crash or get stuck. When the build process fails there is not exit code returned so CI/CD will deploy a broken app on failure. This maybe gets better in future while they keep developing the library.

If you want to create an app that contains articles (i.e. news/blog), Scully can be an awesome technique to use, cause it can convert markdown files to dynamic pages. If you want to do the same in an Angular Universal app you’ll have to add this conversion yourself.

If you are planning on using pre-rendering for a production app you are probably safer using Angular Universal. Scully.io looks very promising, so I’ll follow their journey and see what the future brings us.

--

--