|
| 1 | +include ../_util-fns |
| 2 | + |
| 3 | +:marked |
| 4 | + This cookbook describes how to optimize and deploy your Angular app to production. |
| 5 | + |
| 6 | +a#toc |
| 7 | +:marked |
| 8 | + ## Table of Contents |
| 9 | + * [Overview](#overview) |
| 10 | + * [Simplest deploy possible](#simplest-deploy-possible) |
| 11 | + * [Minimizing your payload](#minimizing-your-payload) |
| 12 | + * [Less files](#less-files) |
| 13 | + * [Smaller files](#smaller-files) |
| 14 | + * [Template compilation](#template-compilation) |
| 15 | + * [Angular configuration](#angular-configuration) |
| 16 | + * [The `base` tag](#the-base-tag) |
| 17 | + * [enableProdMode](#enableprodmode) |
| 18 | + * [Lazy loading](#lazy-loading) |
| 19 | + * [Server configuration](#server-configuration) |
| 20 | + * [Why fallback to `index.html`?](#why-fallback-to-index-html-) |
| 21 | + * [Fallback configuration examples](#fallback-configuration-examples) |
| 22 | + |
| 23 | +a#overview |
| 24 | +.l-main-section |
| 25 | +:marked |
| 26 | + ## Overview |
| 27 | + |
| 28 | + After creating your brand new Angular app you'll want to put it online for the world to see. |
| 29 | + |
| 30 | + Your development setup is optimized for build speed and rapid iteration, but when |
| 31 | + deployment to production you'll want to optimize for loading speed and payload size. |
| 32 | + |
| 33 | + In this chapter you'll see how to deploy your app right now, techniques to reduce your |
| 34 | + payload size, plus Angular and server configuration. |
| 35 | + |
| 36 | +.l-main-section |
| 37 | +:marked |
| 38 | + ## Simplest deploy possible |
| 39 | + |
| 40 | + The simplest possible way to deploy your app is to use your development environment. |
| 41 | + It is already working locally after all, so it should work live. |
| 42 | + |
| 43 | + 1. Copy over your local project folder to your server. |
| 44 | + 2. In your server, edit `index.html` to have the right `<base href="/">` tag. If you are |
| 45 | + using a subfolder (e.g. `www.mysite.com/myapp/`) it should be `/myapp/` otherwise leave it as `/`. |
| 46 | + [More on this later](#angular-configuration). |
| 47 | + 3. Configure your server to redirect requests for missing files to `index.html` instead. |
| 48 | + [More on this later](#server-configuration). |
| 49 | + |
| 50 | + And this is all you need to publish your app! |
| 51 | + |
| 52 | + It's not a very good production deploy though. |
| 53 | + There's more you can do to make our site fast for users. |
| 54 | + |
| 55 | +.l-main-section |
| 56 | +:marked |
| 57 | + ## Minimizing your payload |
| 58 | + |
| 59 | + One of the easiest optimizations you can do is to reduce the code users have to download. |
| 60 | + |
| 61 | + There are several techniques for achieving a smaller payload for your app: |
| 62 | + |
| 63 | + - Bundling: concatenating several modules into a single file (bundle). |
| 64 | + - Inlining: add html and css as strings inside components. |
| 65 | + - Minification: removing all unnecessary whitespace and optional tokens. |
| 66 | + - Uglification: rewriting code to use shorter variable/function names. |
| 67 | + - Dead Code Elimination: removing code/variables that will never used. |
| 68 | + - Tree shaking: removing unused module exports. |
| 69 | + - Ahead-of-Time (AoT) Compilation: pre-compiling Angular templates. |
| 70 | + |
| 71 | + Each of these do different things overall, but they all work together and their effects |
| 72 | + compound on each other. |
| 73 | + |
| 74 | + ### Less files |
| 75 | + Bundling and inlining reduce your app's load time smaller by serving less files and thus |
| 76 | + less round trips. |
| 77 | + For small files, the overwhelming majority of time it takes to get it is not spent downloading, |
| 78 | + but rather communicating with the server and coordinating the transfer. |
| 79 | + You can read more about resource timings in the |
| 80 | + [Chrome DevTools Network Performance page](https://developers.google.com/web/tools/chrome-devtools/network-performance/understanding-resource-timing) |
| 81 | + |
| 82 | + ### Smaller files |
| 83 | + Minification, Uglification and Dead Code Elimination all contribute to reduce the total size |
| 84 | + of your code to the smallest possible amount of bytes, which will result in a smaller |
| 85 | + transfer time. |
| 86 | + |
| 87 | + Tree shaking also reduces the total size but does so by removing whole exports from modules. |
| 88 | + It was popularized by [Rollup](http://rollupjs.org/) and you can read more about it in |
| 89 | + [this post](https://medium.com/@Rich_Harris/tree-shaking-versus-dead-code-elimination-d3765df85c80#.15ih9cyvl). |
| 90 | + |
| 91 | + ### Template compilation |
| 92 | + AoT compilation reduces render time by pre-compiling Angular templates that otherwise would be |
| 93 | + compiled at runtime. |
| 94 | + It also reduces payload size since the Angular compiler itself is no longer needed, and nor are |
| 95 | + directives that are never used in templates. This allows Tree-Shaking to further remove exports. |
| 96 | + |
| 97 | + You can read more about AoT Compilation in our [dedicated cookbook chapter](aot-compiler.html), |
| 98 | + where you'll also find instructions on how to use Rollup to do all the optimizations shown here. |
| 99 | + Another great choice is [Webpack 2](https://webpack.js.org/) together with the |
| 100 | + [Angular Ahead-of-Time Webpack Plugin](https://github.com/angular/angular-cli/tree/master/packages/%40ngtools/webpack). |
| 101 | + |
| 102 | + You can use any build system you like however, the only important thing is to optimize your build. |
| 103 | + Whatever build system you choose make sure to automate it so that you can make a production |
| 104 | + build in a single step. |
| 105 | + |
| 106 | +.l-main-section |
| 107 | +:marked |
| 108 | + ## Angular configuration |
| 109 | + |
| 110 | + ### The `base` tag |
| 111 | + |
| 112 | + There is an important configuration item in the Angular Router that needs to be adjusted for |
| 113 | + different environments: |
| 114 | + [the `base` tags `href` property](https://angular.io/docs/ts/latest/guide/router.html#!#base-href). |
| 115 | + This property tells the router how what the app root URL is so that the router can match routes. |
| 116 | + |
| 117 | + On your development server, your app is most likely served at the root: `http://localhost:3000/`. |
| 118 | + Here you use `<base href="/">`, since `/` is the root of your app. |
| 119 | + |
| 120 | + But depending on the server setup that you use, it can also be served in a subfolder like |
| 121 | + `http://localhost:3000/myapp/` or `http://www.mysite.com/myapp/`, where `/myapp/` is the subfolder. |
| 122 | + In this case you should use `<base href="/myapp/">`, since `/myapp/` is the root of your app and |
| 123 | + not `/`. |
| 124 | + |
| 125 | + When your `base` tag is misconfigured your browser console will show several console, from |
| 126 | + missing files on wrong paths to router errors about missing routes. |
| 127 | + |
| 128 | + ### enableProdMode |
| 129 | + |
| 130 | + You can also configure your Angular app to start on |
| 131 | + [production mode](https://angular.io/docs/ts/latest/api/core/index/enableProdMode-function.html), |
| 132 | + which will make it faster by disabling development specific checks. |
| 133 | + |
| 134 | + Angular apps default do development move, as you can see by the following message on the browser |
| 135 | + console: |
| 136 | + |
| 137 | +code-example(format="nocode"). |
| 138 | + Angular 2 is running in the development mode. Call enableProdMode() to enable the production mode. |
| 139 | + |
| 140 | +:marked |
| 141 | + To enable production mode, add this code to the start of your `main.ts`: |
| 142 | + |
| 143 | +code-example(). |
| 144 | + import { enableProdMode } from '@angular/core'; |
| 145 | + enableProdMode(); |
| 146 | + |
| 147 | +:marked |
| 148 | + ### Lazy loading |
| 149 | + |
| 150 | + The Angular Router allows you to configure NgModules as being |
| 151 | + [Lazy Loaded](https://angular.io/docs/ts/latest/guide/router.html#!#asynchronous-routing), |
| 152 | + which means that particular NgModule (and all it's code) is not loaded on the very first load. |
| 153 | + This allows your app to have a smaller initial load time by not loading modules until they |
| 154 | + are needed. |
| 155 | + |
| 156 | + A common mistake to make while lazy loading a module is to *also* have that module imported via |
| 157 | + a ES6 import. |
| 158 | + If you do that, the module will never really be lazy loaded since the import statement will cause |
| 159 | + it to be imported directly instead of being imported via the Router. |
| 160 | + |
| 161 | + It's important to note that your bundling configuration must take lazy loading into consideration. |
| 162 | + Since there is no ES6 import for that module, normal bundlers don't know that they should also |
| 163 | + separately bundle to module listed in the router config. |
| 164 | + You have to manually create additional bundles for each lazy loaded route. |
| 165 | + |
| 166 | + If you are using Webpack, the |
| 167 | + [Angular Ahead-of-Time Webpack Plugin](https://github.com/angular/angular-cli/tree/master/packages/%40ngtools/webpack) |
| 168 | + will automatically recognize lazy routes. |
| 169 | + |
| 170 | + |
| 171 | +.l-main-section |
| 172 | +:marked |
| 173 | + ## Server configuration |
| 174 | + |
| 175 | + Angular apps are |
| 176 | + [Single Page Applications](https://en.wikipedia.org/wiki/Single-page_application) (SPAs), |
| 177 | + which makes them the perfect candidates to be served by simple static HTML server. |
| 178 | + No preprocessors required! |
| 179 | + |
| 180 | + There is only one configuration item required of a server hosting an Angular app: |
| 181 | + it needs to be able to fallback to `index.html` when being asked for a file the server |
| 182 | + does not have. |
| 183 | + |
| 184 | + ### Why fallback to `index.html`? |
| 185 | + |
| 186 | + When using the Angular Router, it is the one in charge of the routing instead of your server. |
| 187 | + Angular routing defaults to |
| 188 | + [HTML 5 pushState](https://angular.io/docs/ts/latest/guide/router.html#!#browser-url-styles), |
| 189 | + where it manipulates the browser address and history directly. |
| 190 | + So, while the server has the responsability of serving the files, it is always the Angular app |
| 191 | + that will determine which routes are valid and construct the page accordingly. |
| 192 | + |
| 193 | + Imagine you have the `admin/` route for your `www.my-awesome-app.com` Angular app. |
| 194 | + When you visit `www.my-awesome-app.com` most servers will respond with |
| 195 | + the `index.html` located at `www.my-awesome-app.com/index.html`. |
| 196 | + Then you click the link for `admin/` and the your address bar will change |
| 197 | + to `www.my-awesome-app.com/admin/` and you're taken to the admin page. |
| 198 | + |
| 199 | + But if you don't have fallback to `index.html` configured and refresh the page, you will |
| 200 | + get a `404 - Not Found` page! |
| 201 | + Which doesn't make a lot of sense since you just saw that the admin page was there. |
| 202 | + |
| 203 | + When you first clicked the link to `admin/` there was no server request for |
| 204 | + `www.my-awesome-app.com/admin/` because Angular manipulated the browser URL directly. |
| 205 | + |
| 206 | + But when you refresh the `www.my-awesome-app.com/admin/` url, you are not asking *Angular* |
| 207 | + for the admin route but rather *the server*. |
| 208 | + And the server does not have a `www.my-awesome-app.com/admin/index.html` to serve, so it instead |
| 209 | + shows you a 404. |
| 210 | + |
| 211 | + That is why the server needs to be configured to fallback to `index.html`. |
| 212 | + When a user asks the server for any route (that isn't a file), you want the server to *always* |
| 213 | + return default page that boots up Angular. |
| 214 | + After Angular is loaded and bootstrapped the router will look at the address bar and take |
| 215 | + the user where he needs to go. |
| 216 | + |
| 217 | + ### Fallback configuration examples |
| 218 | + |
| 219 | + Different servers are configured in different ways so there is no single configuration that |
| 220 | + works everywhere. |
| 221 | + |
| 222 | + Following are configurations for some of the most popular servers. |
| 223 | + The list is by no means exhausting, but should provide you with a good starting point. |
| 224 | + |
| 225 | + #### Development servers |
| 226 | + |
| 227 | + - [Lite-Server](https://github.com/johnpapa/lite-server): the default dev server used in the |
| 228 | + [Quickstart repo](https://github.com/angular/quickstart), and by default will fallback to |
| 229 | + `index.html`. |
| 230 | + |
| 231 | + - [Webpack-Dev-Server](https://github.com/webpack/webpack-dev-server): you can use the |
| 232 | + `historyApiFallback` entry in the dev server options as follows: |
| 233 | + |
| 234 | +code-example(). |
| 235 | + historyApiFallback: { |
| 236 | + disableDotRule: true, |
| 237 | + htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'] |
| 238 | + } |
| 239 | + |
| 240 | +:marked |
| 241 | + #### Production servers |
| 242 | + |
| 243 | + - [Apache](https://httpd.apache.org/): simply add a |
| 244 | + [rewrite rule](http://httpd.apache.org/docs/current/mod/mod_rewrite.html) |
| 245 | + to your `.htaccess` file as show |
| 246 | + [here](https://ngmilk.rocks/2015/03/09/angularjs-html5-mode-or-pretty-urls-on-apache-using-htaccess/): |
| 247 | +code-example(format="."). |
| 248 | + RewriteEngine On |
| 249 | + # If an existing asset or directory is requested go to it as it is |
| 250 | + RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR] |
| 251 | + RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d |
| 252 | + RewriteRule ^ - [L] |
| 253 | + |
| 254 | + # If the requested resource doesn't exist, use index.html |
| 255 | + RewriteRule ^ /index.html |
| 256 | + |
| 257 | +:marked |
| 258 | + - [NGinx](http://nginx.org/): you can use `try_files` similar to the one described in |
| 259 | + [Front Controller Pattern Web Apps](https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#front-controller-pattern-web-apps), |
| 260 | + modified for serving `index.html`: |
| 261 | + |
| 262 | +code-example(format="."). |
| 263 | + try_files $uri $uri/ /index.html; |
| 264 | + |
| 265 | +:marked |
| 266 | + - [IIS](https://www.iis.net/): you can add a rewrite rule on `web.config`, similar to the one shown |
| 267 | + [here](http://stackoverflow.com/a/26152011/2116927): |
| 268 | +code-example(format="." escape="html"). |
| 269 | + <system.webServer> |
| 270 | + <rewrite> |
| 271 | + <rules> |
| 272 | + <rule name="Angular Routes" stopProcessing="true"> |
| 273 | + <match url=".*" /> |
| 274 | + <conditions logicalGrouping="MatchAll"> |
| 275 | + <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> |
| 276 | + <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> |
| 277 | + </conditions> |
| 278 | + <action type="Rewrite" url="/" /> |
| 279 | + </rule> |
| 280 | + </rules> |
| 281 | + </rewrite> |
| 282 | + </system.webServer> |
| 283 | + |
| 284 | +:marked |
| 285 | + - [GitHub Pages](https://pages.github.com/): you can't |
| 286 | + [directly configure](https://github.com/isaacs/github/issues/408) |
| 287 | + the server used to serve GitHub Pages, but you can add a 404 page. |
| 288 | + Simply copy `index.html` into `404.html`. |
| 289 | + It will still be served with the 404 response, but the app will still load properly. |
| 290 | + It's also a good idea to |
| 291 | + [serve from `docs/` on master](https://help.github.com/articles/configuring-a-publishing-source-for-github-pages/#publishing-your-github-pages-site-from-a-docs-folder-on-your-master-branch) |
| 292 | + and to |
| 293 | + [create a `.nojekyll` file](https://www.bennadel.com/blog/3181-including-node-modules-and-vendors-folders-in-your-github-pages-site.htm) |
| 294 | + |
| 295 | + - [Firebase hosting](https://firebase.google.com/docs/hosting/): you can use a simple |
| 296 | + [rewrite](https://firebase.google.com/docs/hosting/url-redirects-rewrites#section-rewrites). |
| 297 | + |
| 298 | +code-example(format="."). |
| 299 | + "rewrites": [ { |
| 300 | + "source": "**", |
| 301 | + "destination": "/index.html" |
| 302 | + } ] |
| 303 | + |
| 304 | +:marked |
| 305 | + ### CORS |
| 306 | + |
| 307 | + A common issue developers come across when working on SPAs is |
| 308 | + [Cross-origin resource sharing](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) |
| 309 | + errors. |
| 310 | + These are not due to the SPA application itself but rather due to the configuration of APIs with |
| 311 | + which the SPA communicates. |
| 312 | + |
| 313 | + These APIs need to send the `Access-Control-Allow-Origin: *` header on their HTTP responses, |
| 314 | + otherwise the SPA will not be able to call them. |
| 315 | + You can read more about how to enable CORS for specific servers in |
| 316 | + [enable-cors.org](http://enable-cors.org/server.html) |
0 commit comments