100 days of Angular 2 - Day 11: Bring in Angular Universal

I've been very intrigued by the promises of Angular Universal. The idea of running Angular 2 on the server to render the page and serve that to the user is crucial for search engine crawlers. Since many of them do not execute JavaScript*, the page will appear broken and incomplete, so search engines have a hard time properly crawling and indexing these rich, interactive, single-page apps.

Also, if you link to page on Facebook / Slack / Twitter / some other social site, they send a crawler to create a "snapshot" of the page so it appears next to the link. Same as the web crawlers, these do not execute JavaScript and that preview will look broken if that page is part of these rich, single page apps.

Angular, Ember, Backbone, and React all share these issues.

Ember tries to solve this with FastBoot. Backbone has Rendr. React has React Isomorphic.

And Angular 2 solves this with Angular Universal.

* Yes, technically some advanced web crawlers execute JavaScript, but they don't do it very well or very consistently (yet).

How do I use Angular Universal?

First, a quick check. Do you need it?

I took this screenshot from the talk given at Angular U in July 2015 which summarizes three main benefits from Angular Universal.

If you don't care about the initial page load speed, search engine optimization, or social links, then Angular Universal is not worth your time. For example, if you have a corporate application that lives behind a login screen, you may not care about SEO, and your app wouldn't ever show up in your friend's Facebook feed because your friend wouldn't be sharing it there.

However, I have been building social apps, and most of my traffic comes in via Google search results. For me, it's worth bringing in Angular Universal into my project.

So I headed over to Angular Universal page and clicked on the 'Get Started' button which took me to the quick start.

It's a little odd that they still suggest to use typings, since the new, preferred way of declaring types is to use npm's @types. The Angular 2 Webpack Starter they reference does not use typings.

I already have an Angular 2 app, so I didn't use the Angular 2 Webpack Starter. I had some of the dependencies already, but I did need to install angular2-universal and preboot.

I already had a server.ts file that I created on day 10, but just to get something running, I copy + pasted the server.js they listed on the quick start and tried to fire it up.

Okay, let's try

Okay, so that didn't work... After some Googling I'm not sure that their quick start would ever work.

Let's abort out of the quick start and head over to the GitHub repository. They have a link to the documentation and guide.

That takes me to a guide on installation.

They mention there's an Angular Universal Starter, but after cloning it and looking through it, there's a lot going on. Just opening up the package.json is overwhelming with the number of prebuild / build / clean steps, each with a bunch of different options.

It's also worth pointing out the @types listed in the dev dependencies, which means they're not using typings so I am not sure why the official quickstart still has them listed.

Okay, to recap, the quickstart is outdated and doesn't work, and the universal starter is incredibly overwhelming. It assumes a lot about the architecture and it would take me a lot of work to fold my current project into the starter project.

So I'll try to use the starter project as a reference, and follow along with the NodeJS & Express Integration steps.

Node.js and Express

I already have an Angular 2 app, so the first step for me is to copy the webpack.config and tsconfig.json files from the starter app into my app root directory.

I had no issues just replacing my tsconfig.json file with the new one, but I already spent a lot of time setting up my webpack configuration.

Back on day 3 I spent time initially setting Webpack up, then splitting out dev and prod settings on day 4. I further modified my Webpack settings on day 5 to proxy requests to api/ to my backend in my development environment. So I can't exactly just copy and replace the webpack.config settings from the Angular Universal project into my own.

But looking at the webpack.config.js from Angular Universal is... a bit overwhelming. I'm not a Webpack expert so it's not very clear to me what's relevant for Angular Universal and what is just relevant to how the starter project is architectured.

Then to add more confusion to the mix, there's a separate webpack.prod.config.ts to understand.

Okay, maybe for now I should tuck all of my webpack settings (from days 3, 4 and 5) away for a moment and copy over webpack.config.js from Angular Universal to try and get something working.

However, the Webpack starter assumes all code is located in src/, but my project is not structured that way. So I need to modify the lines in the webpack.config.js so it references my client code located at client/ and server code located at server/.

Next it says

Copy the scripts, dependencies and devDependencies from the Universal Starter package.json into your local package.json file

Oh no, this is what I was trying to avoid.

I can't possibly need all of these scripts right now. I'll copy over just a few of them:

"watch": "webpack --watch",
"watch:dev": "npm run server & npm run watch",
"clean:dist": "rimraf dist",
"prebuild": "npm run clean:dist",
"build": "webpack  --progress",
"prestart": "npm run build",
"server": "nodemon dist/server/index.js",
"start": "npm run server",

This should be enough to get Angular Universal working in a dev environment. I already have a start and a build script, so I'll comment mine out for now.

Wait, just kidding, you can't add // to JSON like that. I'll need to rename them.

I have many of the dependencies and devDependencies already, but I'll add the ones that I'm missing.

I modified my server.ts to reference my existing AppModule and brought over the code listed on the documentation.

Because I am using Angular 2.2 (or if I was on 2.1), I need a special workaround, as described somewhere deep in the Angular Universal issues/pulls. You'll notice the same thing in the server.ts from the Angular Universal starter project.

Here's what I have so far:

I am not sure why I'm getting red squiggles for the modules, as they're listed in the package.json and there aren't any issues locating the module if I try to compile the file to JavaScript from TypeScript.

Anyways, the next step is to create my top level NgModule on the server side like this:

Err, wait, I don't know what they're trying to say. But I did add the UniversalModule to my app.module.ts and also my main.ts

Okay, all done following the steps in the documentation. What's next?

Fire it up

So it's not super clear in the documentation, but I think I need to run npm start and npm run watch to get the development environment started.

Let's begin with npm start

So that's just peachy. Googling around wasn't very fruitful, but I started to gather it was a Webpack issue, as it seems Webpack can't understand the export statement.

I took a look at my package.json and realized I am using webpack 1.13.2 whereas the Angular Universal Starter is using webpack 2.1.0-beta.27.

This is probably the cause of the error, but I'll need to investigate and troubleshoot on day 12 because I'm out of time today.