Skip to main content

Angular Production Build

Introduction

When your Angular application is ready to be deployed to a production environment, you need to create what's called a "production build" - a version of your application that's optimized for performance and security. Unlike development builds, which prioritize debugging and fast rebuild times, production builds focus on delivering the smallest, fastest application possible to end users.

In this guide, we'll walk through the process of creating production builds in Angular, understanding configuration options, and implementing best practices to ensure your application performs optimally in the real world.

Understanding Angular Build Configurations

Angular uses the Angular CLI (Command Line Interface) to create builds with different optimization settings. The Angular CLI's build configurations are defined in the angular.json file in your project root.

Default Build Configurations

By default, Angular provides two main build configurations:

  • development - Used during development with faster build times but minimal optimizations
  • production - Used for deployment with full optimizations but longer build times

Let's look at a typical configuration in angular.json:

json
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"outputHashing": "all",
"optimization": true,
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true
}
}

Creating a Production Build

Basic Production Build Command

To create a production build of your Angular application, use the following CLI command:

bash
ng build --configuration production

Or simply:

bash
ng build

Since Angular 12, the CLI defaults to a production build when using ng build without specifying a configuration.

Output of a Production Build

After running the build command, Angular generates the production-ready files in the dist/ folder (or the output path specified in your angular.json). The output typically includes:

  • HTML, CSS, and JavaScript files (bundled and minified)
  • Assets like images and fonts
  • Source maps (if enabled)

A typical build output structure looks like:

dist/
└── your-app-name/
├── favicon.ico
├── index.html
├── main.12345abcde.js
├── polyfills.67890fghij.js
├── runtime.54321edcba.js
├── styles.13579klmno.css
└── assets/
└── ...

Production Build Optimizations

Angular automatically applies several optimizations during production builds:

1. Ahead-of-Time (AOT) Compilation

In production builds, Angular uses AOT compilation by default. This compiles your templates at build time rather than runtime:

Benefits:

  • Faster rendering
  • Smaller application size (no need to ship the Angular compiler)
  • Early template error detection
  • Better security

2. Bundle Size Reduction

Angular production builds minimize JavaScript and CSS files through:

  • Tree shaking: Removes unused code
  • Minification: Reduces file size by removing whitespace and shortening variable names
  • Uglification: Transforms code to use shorter names and optimized patterns

3. Differential Loading (Modern Browsers vs Legacy)

For Angular 8+, differential loading is enabled by default in production builds. This creates:

  • A modern bundle (ES2015+) for modern browsers
  • A legacy bundle (ES5) for older browsers

This ensures optimal performance without sacrificing compatibility.

Customizing Production Builds

Environment-Specific Configurations

Angular uses environment files to manage environment-specific settings:

  1. Create environment files for different environments:
typescript
// src/environments/environment.ts (default)
export const environment = {
production: false,
apiUrl: 'http://localhost:3000/api'
};
typescript
// src/environments/environment.prod.ts
export const environment = {
production: true,
apiUrl: 'https://api.yourproductionapp.com/api'
};
  1. Use these values in your code:
typescript
import { environment } from '../environments/environment';

@Injectable({
providedIn: 'root'
})
export class ApiService {
private baseUrl = environment.apiUrl;

constructor(private http: HttpClient) { }

getData() {
return this.http.get(`${this.baseUrl}/data`);
}
}

Custom Build Configurations

You can create custom build configurations for different environments (staging, QA, etc.):

  1. Add a new configuration in angular.json:
json
"configurations": {
"production": {
// existing production config
},
"staging": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.staging.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true
}
}
  1. Create the corresponding environment file:
typescript
// src/environments/environment.staging.ts
export const environment = {
production: false,
staging: true,
apiUrl: 'https://staging-api.yourapp.com/api'
};
  1. Build using the custom configuration:
bash
ng build --configuration staging

Advanced Production Build Options

Budget Configuration

Angular allows you to set size budgets for your application to prevent it from becoming too large:

json
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
]

If your build exceeds the warning size, the CLI will issue a warning. If it exceeds the error size, the build will fail.

Source Maps for Production Debugging

Sometimes you need source maps in production for error tracking. You can enable this in your configuration:

json
"configurations": {
"production": {
// other settings
"sourceMap": {
"scripts": true,
"styles": false,
"hidden": true,
"vendor": false
}
}
}

With hidden: true, source maps are generated but not referenced in the bundle files, making them available for error tracking tools but not browser DevTools.

Real-World Example: Multi-Environment Configuration

Let's create a complete example with development, staging, and production environments:

  1. First, create the environment files:
typescript
// src/environments/environment.ts
export const environment = {
production: false,
environmentName: 'development',
apiUrl: 'http://localhost:3000/api',
debugLogging: true,
featureFlags: {
newUserDashboard: true,
experimentalFeatures: true
}
};
typescript
// src/environments/environment.staging.ts
export const environment = {
production: false,
environmentName: 'staging',
apiUrl: 'https://staging-api.yourapp.com/api',
debugLogging: true,
featureFlags: {
newUserDashboard: true,
experimentalFeatures: false
}
};
typescript
// src/environments/environment.prod.ts
export const environment = {
production: true,
environmentName: 'production',
apiUrl: 'https://api.yourapp.com/api',
debugLogging: false,
featureFlags: {
newUserDashboard: true,
experimentalFeatures: false
}
};
  1. Configure the build options in angular.json:
json
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
}
]
},
"staging": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.staging.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true
}
}
  1. Create build scripts in package.json:
json
"scripts": {
"build": "ng build",
"build:staging": "ng build --configuration staging",
"build:prod": "ng build --configuration production"
}
  1. Use the environment in a service:
typescript
import { Injectable } from '@angular/core';
import { environment } from '../environments/environment';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable({
providedIn: 'root'
})
export class DataService {
constructor(private http: HttpClient) {}

getData(): Observable<any[]> {
if (environment.debugLogging) {
console.log(`Fetching data from ${environment.apiUrl}/data`);
}

return this.http.get<any[]>(`${environment.apiUrl}/data`)
.pipe(
tap(data => {
if (environment.debugLogging) {
console.log('Data received:', data);
}
})
);
}

shouldShowExperimentalFeatures(): boolean {
return environment.featureFlags.experimentalFeatures;
}
}

Best Practices for Production Builds

  1. Always test production builds before deployment

    • The production build might behave differently than the development version
  2. Keep an eye on bundle size

    • Use the budgets feature to prevent bloat
    • Consider code splitting for large applications
  3. Use environment-specific configurations properly

    • Never hardcode sensitive information like API keys
    • Use different API endpoints for different environments
  4. Optimize assets

    • Compress images
    • Consider using modern formats like WebP
    • Use a CDN for static assets when possible
  5. Enable Gzip/Brotli compression on your server

    • Further reduces the size of transmitted files
  6. Set up proper caching headers

    • Angular's output hashing helps with cache busting
  7. Consider using a CI/CD pipeline

    • Automate the build and deployment process
    • Run tests before deploying

Troubleshooting Common Production Build Issues

1. Build exceeds budget size

Solution: Split your code into lazy-loaded modules:

typescript
// In your app-routing.module.ts
const routes: Routes = [
{
path: 'feature',
loadChildren: () => import('./feature/feature.module').then(m => m.FeatureModule)
}
];

2. Missing environment variables in production

Solution: Double-check your environment files and file replacements in configurations.

3. API calls failing in production

Solution: Verify that you're using environment.apiUrl consistently and that CORS is properly configured on your API.

Summary

Creating production builds in Angular is a crucial step in the deployment process. The Angular CLI provides powerful tools for optimizing your application for production environments, including code minification, tree shaking, and AOT compilation.

By understanding build configurations and leveraging environment-specific settings, you can create efficient, secure applications ready for real users. Remember to follow best practices around bundle size optimization, environment configurations, and testing to ensure a smooth deployment process.

Additional Resources

Exercises

  1. Create custom build configurations for development, staging, and production environments with different API endpoints.

  2. Set up a lazy-loaded feature module and observe how it affects your production bundle size.

  3. Implement feature flags using environment configurations to enable/disable features based on the build environment.

  4. Configure source maps for a production build and use a tool like Sentry to track errors.

  5. Set up a CI/CD pipeline using GitHub Actions or another service to automatically build and deploy your Angular application when you push changes to the main branch.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)