Mert Özen Her Satırda Daha İleri
Frontend Angular — #19

Angular Build ve Deploy: ng build, Environment Dosyaları, Budgets ve SPA Routing

Mert Özen 31 Eki 2026 7 dk 1 görüntülenme
Angular Build ve Deploy: ng build, Environment Dosyaları, Budgets ve SPA Routing

Angular 12'de ng build ile production paketi: dist çıktısı, fileReplacements ile environment dosyaları, budgets, base href, Nginx/IIS'te SPA routing 404 sorunu, source map ve bundle analizi.

Sürüm notu: Bu yazıdaki kod ve komutlar Angular 12 içindir.

Provadan Gala Gecesine

Şimdiye kadar her şeyi ng serve ile çalıştırdık. ng serve geliştirme için harika bir araç: kodu bellekte derler, kaydettiğin anda tarayıcıyı yeniler, hata mesajlarını okunaklı tutar. Ama bir tiyatro provası gibidir. Oyuncular sahnededir, ışıklar yanar, ama salonda seyirci yoktur ve kimse bilet almamıştır.

Gala gecesi için başka bir hazırlık gerekir. Kod küçültülmeli, gereksiz parçalar atılmalı, dosyalar herhangi bir web sunucusunun servis edebileceği düz HTML, JavaScript ve CSS hâline gelmeli. Bu yazıda ng build ile bu paketi üretecek, ayarlarını tanıyacak ve sonra onu gerçek bir sunucuya koyacağız.

ng build ve dist Klasörü

Proje klasöründe şunu çalıştır:

ng build

Angular 12 ile gelen önemli bir değişiklik var: ng build artık varsayılan olarak production configuration ile çalışıyor. Eski projelerde gördüğün ng build --prod bayrağı Angular 12'de deprecated oldu; ona gerek kalmadı. Development ayarlarıyla build almak istersen bunu açıkça söylemen gerekiyor:

ng build --configuration development

Build bitince proje kökünde bir dist klasörü oluşur. İçine bakalım:

dist/magaza/
  3rdpartylicenses.txt
  assets/
  favicon.ico
  index.html
  main.3c1a2f9e8b7d6c5a4b3e.js
  polyfills.8d4e1b2c3a5f6e7d9c0b.js
  runtime.a7b6c5d4e3f2a1b0c9d8.js
  styles.0f1e2d3c4b5a69788796.css

Dağıtacağın şey tam olarak bu klasörün içeriği. main senin kodun, polyfills daha önce gördüğümüz Zone.js ve tarayıcı yamaları, runtime ise parçaları yükleyen küçük bir yükleyici. 3rdpartylicenses.txt, kullandığın paketlerin lisanslarını toplar.

Dosya adlarındaki o uzun karakter dizileri içerikten üretilen hash'lerdir. Kodun değişirse hash de değişir, tarayıcı yeni dosyayı yeni bir dosya olarak görür. Böylece kullanıcının tarayıcısı eski bir JavaScript dosyasını önbellekte tutup durmaz. Buna cache busting denir.

Production ve Development Configuration'ları

Bu davranışın nereden geldiğini görmek için angular.json'daki build bölümüne kısaltılmış hâliyle bakalım:

"build": {
  "builder": "@angular-devkit/build-angular:browser",
  "options": {
    "outputPath": "dist/magaza",
    "index": "src/index.html",
    "main": "src/main.ts",
    "assets": ["src/favicon.ico", "src/assets"],
    "styles": ["src/styles.css"]
  },
  "configurations": {
    "production": {
      "budgets": [ ... ],
      "fileReplacements": [ ... ],
      "outputHashing": "all"
    },
    "development": {
      "buildOptimizer": false,
      "optimization": false,
      "vendorChunk": true,
      "extractLicenses": false,
      "sourceMap": true,
      "namedChunks": true
    }
  },
  "defaultConfiguration": "production"
}

options her build'de geçerli olan ortak ayarlar. configurations ise bunların üzerine yazılan profiller. defaultConfiguration: "production" satırı, hiçbir şey söylemediğinde hangi profilin kullanılacağını belirliyor. serve bölümünde ise bu değer development; ng serve bu yüzden hızlı ve okunaklı çalışıyor.

Production profilinde ayrıca yazılmamış ama varsayılan olarak açık gelen ayarlar var: kodun küçültülmesi, kullanılmayan kodun atılması (tree-shaking) ve lisansların ayrı dosyaya toplanması. Development profili bunları tek tek kapatıyor. Bu yüzden aynı uygulama development'ta birkaç megabayt, production'da birkaç yüz kilobayt tutabilir.

Environment Dosyaları ve fileReplacements

Daha önce HttpClient konusunda API adresini environment dosyasından okumuştuk. Şimdi o dosyaların nasıl yer değiştirdiğini görelim:

// src/environments/environment.ts
export const environment = {
  production: false,
  apiUrl: 'http://localhost:3000/api'
};

// src/environments/environment.prod.ts
export const environment = {
  production: true,
  apiUrl: 'https://api.magaza.com'
};

Kodda her zaman environment.ts'yi import edersin, environment.prod.ts'ye hiç dokunmazsın. Değişimi production profilindeki şu ayar yapar:

"fileReplacements": [
  {
    "replace": "src/environments/environment.ts",
    "with": "src/environments/environment.prod.ts"
  }
]

Build sırasında CLI, environment.ts'nin yerine sessizce environment.prod.ts'yi koyar. Bir tiyatro dekoru gibi: oyun aynı, sadece perde açıldığında arkadaki fon değişmiş. main.ts'deki if (environment.production) { enableProdMode(); } bloğu da bu sayede çalışır ve Angular'ın development modundaki ek kontrollerini kapatır.

İkiden fazla ortamın varsa, mesela bir test sunucun, environment.staging.ts oluşturup configurations altına kendi fileReplacements'ıyla bir staging profili eklersin ve ng build --configuration staging ile build alırsın. Burada küçük ama önemli bir uyarı: environment dosyaları paketin içine gömülür ve tarayıcıda herkes okuyabilir. Gizli anahtarlar oraya yazılmaz.

Budgets: Paket Boyutuna Bekçi

Production profilindeki budgets bölümü, ng new'in strict mode varsayılanıyla şöyle gelir:

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

initial, uygulama açılırken indirilen bütün dosyaların toplam boyutu. anyComponentStyle ise tek bir component'in stil dosyası için sınır. Uyarı eşiği aşılırsa build tamamlanır ama terminalde sarı bir uyarı görürsün. Hata eşiği aşılırsa build başarısız olur. Mesaj şuna benzer:

Warning: initial exceeded maximum budget. Budget 500.00 kB was not met by 84.31 kB with a total of 584.31 kB.

Budget, araca konan bir hız sınırlayıcı gibidir. Kimse bilerek 3 MB'lık bir paket yayınlamaz; ama her hafta eklenen küçük bir kütüphane bir gün oraya varır. Bu uyarıyı gördüğünde ilk tepkin sınırı yükseltmek değil, nedenini araştırmak olsun. Daha önce gördüğümüz lazy loading ilk paketi küçültmenin en etkili yollarından biri.

base href: Uygulama Alt Klasördeyse

src/index.html'i açarsan head içinde şu satırı görürsün:

<base href="/">

Bu satır tarayıcıya ve Angular router'ına "bütün göreli adresleri buradan başlat" der. Uygulaman alan adının kökünde, yani https://magaza.com/ adresinde yayınlanacaksa olduğu gibi kalabilir. Ama https://ornek.com/magaza/ gibi bir alt klasöre koyacaksan JavaScript dosyaları yanlış yerde aranır ve beyaz bir sayfayla karşılaşırsın. Bunun için build sırasında base href'i değiştirirsin:

ng build --base-href /magaza/

Sondaki eğik çizgiyi unutma; /magaza ile /magaza/ aynı şey değil. Aynı ayarı angular.json'da ilgili configuration içine "baseHref": "/magaza/" olarak da yazabilirsin.

Statik Hosting ve 404 Sorunu

dist klasörünü bir sunucuya kopyaladın, ana sayfa açıldı, menüden /urunler/42 sayfasına geçtin, her şey yolunda. Sonra sayfayı yeniledin ve 404 aldın. Bu, Angular uygulamasını ilk kez deploy eden hemen herkesin yaşadığı bir an.

Sebebi şu: menüden geçiş yaptığında sunucuya hiç gidilmedi, daha önce gördüğümüz router adresi tarayıcının içinde değiştirdi. Sayfayı yenilediğinde ise tarayıcı sunucuya gerçekten /urunler/42 diye bir dosya soruyor. Sunucunun diskinde böyle bir dosya yok, o yüzden 404 dönüyor. Çözüm, sunucuya "bulamadığın her adres için index.html'i ver" demek. Gerisini Angular router halleder.

Nginx

server {
  listen 80;
  server_name magaza.com;
  root /var/www/magaza;
  index index.html;

  location / {
    try_files $uri $uri/ /index.html;
  }
}

try_files önce istenen dosyayı, sonra aynı isimde bir klasörü arar; ikisi de yoksa index.html'i döner. Böylece main.3c1a....js gibi gerçek dosyalar normal servis edilir, geri kalan her adres Angular'a düşer.

IIS

IIS'te aynı işi URL Rewrite modülü yapar. src altına bir web.config dosyası koy:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="Angular Routes" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
          </conditions>
          <action type="Rewrite" url="/" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Bu dosyanın dist'e kopyalanması için angular.json'daki assets dizisine "src/web.config" eklemeyi unutma. Sunucu ayarına hiç erişimin yoksa son çare olarak router'ı RouterModule.forRoot(routes, { useHash: true }) ile hash modunda çalıştırabilirsin; adresler /#/urunler/42 şeklinde olur ve sunucuya hiç gitmez. Çalışır ama adresler pek şık durmaz.

Source Map ve Bundle Analizi

Production'da bir hata aldığında konsolda main.3c1a....js:1 gibi tek satırlık, küçültülmüş bir kod görürsün. Source map, küçültülmüş kodu senin yazdığın TypeScript satırlarına geri eşleyen haritadır. Production profilinde varsayılan olarak kapalıdır; gerektiğinde açabilirsin:

ng build --source-map

Source map dosyalarını herkese açık bir sunucuya koyarsan kaynak kodunu da yayınlamış olursun. Genelde ya hiç yüklenmez ya da sadece hata takip aracına gönderilir.

Paketin neden büyük olduğunu merak ettiğinde ise bundle analizi yaparsın:

ng build --stats-json
npx webpack-bundle-analyzer dist/magaza/stats.json

--stats-json, build'in içeriğini anlatan bir stats.json dosyası üretir. webpack-bundle-analyzer bu dosyayı renkli bir harita olarak tarayıcıda açar; her kutu bir paket, kutunun büyüklüğü de boyutu. Çoğu zaman suçlu hemen göze çarpar: tek bir fonksiyonu için eklenmiş koca bir tarih kütüphanesi ya da tamamı import edilmiş bir ikon seti.

Küçük Bir Deneme

Önceki yazılarda kurduğun örneklerden birini al ve önce ng build --configuration development, sonra ng build çalıştır. İki dist klasörünün boyutunu ve dosya adlarını karşılaştır. Ardından environment.prod.ts'ye farklı bir apiUrl yaz ve production build'de doğru adresin kullanıldığını doğrula.

Sonra dist içeriğini yerelde basit bir statik sunucuyla aç: npx serve dist/magaza. Routing'i olan bir projede bir alt sayfaya git ve sayfayı yenile; 404'ü kendi gözünle gör. Ardından sunucuyu npx serve -s dist/magaza ile yeniden başlat. -s bayrağı bulunamayan her adresi index.html'e yönlendirir, yani Nginx'teki try_files ayarının yaptığı işi yapar. Sorunun çözüldüğünü kontrol et.

Bununla Angular 12 temellerini tamamladık: ilk component'ten teste, build'den deploy'a kadar bir uygulamanın bütün yolunu birlikte yürüdük. Ama Angular yerinde durmuyor; her altı ayda bir yeni bir ana sürüm çıkıyor. Serinin bir sonraki bölümünde Angular'ı sürüm sürüm takip etmeye başlıyoruz: ng update ile projeyi Angular 13'e taşıyacak ve o sürümün neyi değiştirdiğini göreceğiz.