Angular Universal with Nrwl Nx

Bootstrapping a Nrwl Nx Workspace with Angular-Universal

Angular Universal is a project for Angular allowing Server-Side-Rendering, allowing faster loading times and better SEO.

Nx is a project by Nrwl, a company of former Google engineers, trying to share some of Google’s best practices with us plebs. The biggest advantage of Nx is its tooling and bootstrapping support for mono-repos. A mono-repo is simply a way of managing multiple applications with shared libraries in a single workspace.

However, bootstrapping an Angular app with Nx schemas does not work so well with Angular Universal yet. This tutorial will help with that ❤️.

1. Generate the app

Starting from scratch, let us download the Angular-CLI and NX globally:

npm install -g @angular/cli

npm install -g @nrwl/schematics

Now let us generate a new workspace called “workspace” and a new app called “blog”:

create-nx-workspace blog

Prompt CSS => Choose SCSS

Prompt npm-scope => type “workspace” for aliases like “@workspace”

Prompt framework => Choose “none” for now

cd workspace

ng g app blog

Prompt framework => choose Angular

Prompt directory => press enter (default)

Prompts unit-tester => choose Jest (❤️)

e2e-tester => choose Cypress (❤️❤️)

tags => write “app, blog-app” (or whatever you want)

Now you should have a workspace with a structure like this:

Let us utilise native Angular-CLI schematics to try and bootstrap Angular Universal into the project:

ng add @nguniversal/express-engine --clientProject blog

The schematics will generate all the files that we need, but some of them will be placed in the wrong place (for a mono-repo) and they will not be linked correctly.

server.ts and webpack.server.config.js were generated in the root directory of the workspace. We want to put them in apps/blog instead. Also note the new commands in package.json.

2. Bootstrap everything

Our goals for the rest of the tutorial will be to

To put server.ts and webpack.server.config.js in apps/blog To make Angular generate the server- and browser-output in dist/apps/blog/server/ and dist/apps/blog/browser/, respectively

But let’s start step-by-step. If we try to execute the newly generated package.json-commands

npm run build:ssr && npm run serve:ssr

… we will get the error that the file (for production file-replacement ) at path /workspace/src/environments/environment.prod.ts was not found. In angular.json change the server-fileReplacements to have the apps/blog-prefix:

"server": {

...

"configurations": {

"production": {

"fileReplacements": [

{

"replace": "apps/blog/src/environments/environment.ts",

"with": "apps/blog/src/environments/environment.prod.ts"

}

]

}

}

}

If you execute the commands now, you might get some confusing Cypress-related errors. But don’t worry, once we fix our project structure, they will go away.

So let’s move server.ts and webpack.server.config.js into apps/blog. server.ts will produce some typescript-compilation errors (“require”, “path” etc cannot be found) because tsconfig.app.json contains the line:

"types": []

…overriding “node”-types from the root-level tsconfig.json; delete this line ☠️

The other files generated for Angular Universal inside apps/blog are ok as they are and we don’t have to touch them for the rest of the tutorial.

Now let’s adjust two paths in server.ts according to our desired dist-folder structure:

const DIST_FOLDER = join(process.cwd(), 'dist', 'apps', 'blog', 'browser');



const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('../../dist/apps/blog/server/main');

And in webpack.server.config.js link to the moved server.ts file and also adjust the output directory:

server: './apps/blog/server.ts' output: {

path: path.join(__dirname, '../../dist/apps/blog/server'),

filename: '[name].js'

},

Now inside package.json, link the compile:server-command to the moved webpack.server.config.js and link the serve:ssr-command to the new output directory:

"compile:server": "webpack --config apps/blog/webpack.server.config.js --progress --colors", "serve:ssr": "node dist/apps/blog/server/server",

Now that we expect our output to be in dist/apps/blog, let us instrument Angular to actually put it there. In angular.json change the paths for the browser and the server output:

While you are in angular.json, you might as well add the following to server.configurations:

"sourceMap": false,

"optimization": true

This will remove the source maps and optimize/minify the server-output. Otherwise at the end you will get a source map like this for your main.js:

3. Profit!

You can verify that you did everything correctly by running this command again:

npm run build:ssr && npm run serve:ssr

On http://localhost:4000 you should now be able to see the server-side app:

You can tell it is prerendered because the <body>-tag contains more than just an empty root-tag, but actual HTML-tags like <div>, <h1> etc inside of it. You can also serve the browser-side app as usual to see the difference.

4. Summary

You can find the code from this tutorial here:

If you found this tutorial useful, please 👏 one or few times! Also big thanks to Stefan Karlsson for the help on Stackoverflow!