ng build -prod
in the Angular CLI gets you about 90% of the way there by applying things like AOT, minification, code splitting, etc, but there's more you can do to help your users have a fast, rich experience with your application.Here are 5 best practices you should think about when using Angular in production. These are based on the conversations I've had with developers over the last couple of months, as well as based on what I see in real world web applications.
Best Practice 1: Measure!
As always, my #1 recommendation is going to be to measure your applications for size and performance.Understand how much you are shipping
First, check out and understand the JavaScript you are shipping to the browser. The Angular team is working to make Angular smaller to make these goals easier, but as a rule of thumb you can think about uncompressed bundle size as follows:- > 1 MiB : ( - Maybe you could lazy load more code, or require fewer dependencies?
- > 500 KiB : / - Angular 4.2.4 itself is around 230 KiB, so this isn't necessarily a easy task. Are there ways for you to at least make your index or home page this small?
- < 300KiB : ) - You are doing a lot for your users, but always look for even more ways of improving things.
Typically I recommend source-map-explorer and analyzing your JavaScript maps generated from
ng build -prod -sm
(Production build WITH source maps).Understand how browsers load your code
After you have made the smallest bundle you can for your users, it will help to measure how browsers really load your application.You can do this entirely in Chrome's Dev Tools, but if you are looking for a more independent or consistent analysis, check out WebPageTest. Running your site through WebPageTest, especially using older devices or slower internet connections, can give you insight about the experiences you are shipping to users.
The site will load your application, show you the waterfall of network requests, show you when the device is CPU bound, and more. There's even an option to run a Lighthouse audit as well As you are making improvements to your application, or applying best practices, it can be exciting to watch seconds fall off of your loading time.
Best Practice 2: SSL
We now live in a world where every public web application deserves an SSL Certificate, increasing the security of your application, and increasing user trust that the application you are trying to send them hasn't been modified in any way.Fortunately getting SSL certificates are relatively easy (and usually cost-free).
My some of my favorite ways of getting an SSL Certificate:
- Use Firebase Hosting and click on Hosting->Connect Domain. You point your domain or subdomain at their IPs and they'll automatically provision SSL for your application
- Use Let's Encrypt - They have designed a protocol and clients that work on Windows, Mac, Linux, and across many different servers like
nginx
,Apache
,IIS
and many others. Typically you run their client such ascertbot
on your server about every 90 days and they'll automatically provision and install or renew your certificate. - Use a host like Heroku who automatically provision and manages SSL for you
SSL is essential to not only for increasing user trust, but from a technical standpoint, many new technologies are being built with SSL as a requirement (Web Image Capture API, HTTP2, Service Workers, and many more)
Best Practice 3: HTTP2
HTTP 1.x was a great protocol which made the web possible, but it wasn't optimized for the way we write and ship applications in 2017 and beyond, so the standards bodies designed a better protocol.This newer protocol has two important differences:
- Make only a single connection to the server, over which we can request and send multiple files
- Allow the server to initiate file downloads based on requests
HTTP2 is a really good idea for developers because it means that even without changing a single line of code, your users will be able to download your files faster, decreasing load time.
ngingx
, IIS
, Apache
, Firebase
, and more all support HTTP2, although you may need to make sure you are using a recent version, and often you'll need to turn on support for HTTP2 manually.Best Practice 4: Server Push
To take full advantage of the benefits of HTTP2, you need to configure server push.Server Push allows web developer to say, "You requested my index page! You're going to need a few more files to fully load that page". This means that instead of just sending only the static flat HTML file, you can "server push" and initiate transfer of your CSS, JavaScript, Images, or other files that you already knew your users would need to download.
This doesn't sound like a big deal at first, but it can literally mean cutting seconds off of your load time for your users because they don't need to wait for the index page to be fully loaded, then parsed, in order to initiate the other network requests. If you have a long request chain (index loads css, which loads another css, which loads a font file), this can make an even bigger improvement.
Configuring HTTP2 Push
With Firebase, HTTP2 Push for Angular is relatively straightforward. The Angular CLI is looking at baking full support for server push into the build process for multiple server environments, but I typically just use the following script that I callgenerate-http2-push.js
to update my firebase.json
configuration.fs = require('fs');
fs.readdir('dist/', (err, files) => {
if(!files) {
console.log("No dist folder found to write to.");
return;
}
// Ignore some files
files = files.filter(file => {
if(file.indexOf('favicon') != -1) {
return false
}
if(file.indexOf('index.html') != -1) {
return false;
}
if(fs.lstatSync(`dist/${file}`).isDirectory()) {
return false;
}
if(file.indexOf('.bundle.') != -1 || file.indexOf('.chunk.')) {
return true;
} else {
return false;
}
})
let result = '';
for(let file of files) {
let type;
if(file.substr(-3) == "css") {
type = 'style';
} else {
type = 'script';
}
result += `</${file}>;rel=preload;as=${type},`
}
updateWith(result);
})
function updateWith(result) {
fs.readFile('firebase.json', 'utf8', function(err,data) {
if(err) {
return console.log(err);
}
let re = /(\w*"headers": \[{"key": "Link", "value": ")(.*)("}\])/g;
if(re.exec(data)) {
let newConfig = data.replace(re , `$1${result}$3`);
fs.writeFile('firebase.json', newConfig, 'utf8', function(err) {
if (err) return console.log(err);
console.log("firebase.json updated successfully.");
})
} else {
console.log("Couldn't find a valid firebase config to update.");
return;
}
});
}
Now I realize this uses regular expressions and isn't perfect, and that Alex Rickabaugh's ng-pwa-tools does a much better job of generating this configuration based on the actual routes (including lazy ones) discovered in your application, but this tool gets me started.
Once I have this script on disk, I'll typically:
ng build -prod
tools/generate-http2-push.js
firebase deploy
and save this as a
deploy.sh
command.Best Practice 5: Use a loader for web fonts
I love web fonts, and ever designer I've worked with loves them too. They make a site feel much more modern very quickly.The problem with web fonts for developers is that if you install them the way Google Fonts recommends, you'll actually block the parsing and execution of your JavaScript until after the font has loaded, which implies opening another TCP connection, resolving DNS, and a bunch of other very slow asynchronous processes.
The best answer to this that I have found so far is to use a font loader to take this out of the critical path.
There's a great tool by Google/Adobe called WebFont loader that I typically download and save as
webfont.js
in my project (and add to my .angular-cli.json
configuration's static file list). Then I change my index.html
:Before
<link href="https://fonts.googleapis.com/css?family=Montserrat|Roboto+Mono" rel="stylesheet">
After
<script src="./webfont.js"></script>
<script>
WebFont.load({
google: {
families: ['Montserrat', 'Roboto Mono']
}
});
</script>