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
:
"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:
ng build --configuration production
Or simply:
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:
- Create environment files for different environments:
// src/environments/environment.ts (default)
export const environment = {
production: false,
apiUrl: 'http://localhost:3000/api'
};
// src/environments/environment.prod.ts
export const environment = {
production: true,
apiUrl: 'https://api.yourproductionapp.com/api'
};
- Use these values in your code:
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.):
- Add a new configuration in
angular.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
}
}
- Create the corresponding environment file:
// src/environments/environment.staging.ts
export const environment = {
production: false,
staging: true,
apiUrl: 'https://staging-api.yourapp.com/api'
};
- Build using the custom configuration:
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:
"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:
"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:
- First, create the environment files:
// src/environments/environment.ts
export const environment = {
production: false,
environmentName: 'development',
apiUrl: 'http://localhost:3000/api',
debugLogging: true,
featureFlags: {
newUserDashboard: true,
experimentalFeatures: true
}
};
// 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
}
};
// 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
}
};
- Configure the build options in
angular.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
}
}
- Create build scripts in
package.json
:
"scripts": {
"build": "ng build",
"build:staging": "ng build --configuration staging",
"build:prod": "ng build --configuration production"
}
- Use the environment in a service:
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
-
Always test production builds before deployment
- The production build might behave differently than the development version
-
Keep an eye on bundle size
- Use the budgets feature to prevent bloat
- Consider code splitting for large applications
-
Use environment-specific configurations properly
- Never hardcode sensitive information like API keys
- Use different API endpoints for different environments
-
Optimize assets
- Compress images
- Consider using modern formats like WebP
- Use a CDN for static assets when possible
-
Enable Gzip/Brotli compression on your server
- Further reduces the size of transmitted files
-
Set up proper caching headers
- Angular's output hashing helps with cache busting
-
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:
// 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
-
Create custom build configurations for development, staging, and production environments with different API endpoints.
-
Set up a lazy-loaded feature module and observe how it affects your production bundle size.
-
Implement feature flags using environment configurations to enable/disable features based on the build environment.
-
Configure source maps for a production build and use a tool like Sentry to track errors.
-
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! :)