Angular 8’de Angular Universal İle Server-Side Rendering (SSR)

Merhaba,

Angular, mimarisel olarak kullanıcıdan alınan istek neticesinde üretilen componenti tarayıcıya göndermekte ve yapısal olarak client tabanlı bir değişiklikle sayfanın tekrar renderına ihtiyaç duymaksızın ekrana basmaktadır. Böylece tarayıcı üzerinde elde edilen çıktının SEO(Search Engine Optimization – Arama Motoru Optimizasyonu) açısından önem teşkil eden kaynak kodlarının ilk render edildiği gibi kalması neticesinde sayfa geçişlerindeki bu staticlik bizleri olumsuz etkilemektedir. İşte Angular mimarisindeki bu handikapı Angular uygulamasını sunucu tarafında işlememizi sağlayan Angular Universal ile gerçekleştirdiğimiz Server-Side Rendering(SSR) ile aşabilmekteyiz.

Angular Universal Nedir?

Angular uygulamalarının sunucu tarafında handle edilmesidir. Dolayısıyla sunucuda ele alınan component içerisinde varsa API taleplerini beklemekte, gelen verileri uygun yerlere yerleştirmekte ve nihayetinde tüm sayfayı tam teferruatlı oluşturduktan sonra tarayıcıya göndermektedir.

Ön Hazırlık

Makalede örneklendirmede kullanabilmek için “home”, “about” ve “contact” olmak üzere üç adet component oluşturalım.

ng g c components/home --spec false
ng g c components/about --spec false
ng g c components/contact --spec false

Oluşturduğumuz bu componentlerin html dosyalarının içini aşağıdaki gibi dolduralım.
home.component.html;

<h1>Anasayfa</h1>
<p>Hoşgeldiniz...</p>

about.component.html;

<h1>Biz Kimiz?</h1>
<p>
   Beynimiz yorulmadan geceleri uyuyamayanlardanız...
</p>

contact.component.html;

<h1>İletişim</h1>
<p>Bize ulaşmak için Türkçe kullanınız...</p>
<form>
   <table>
      <tbody>
         <tr>
            <td>Adınız</td>
            <td>:</td>
            <td><input type="text"></td>
         </tr>
         <tr>
            <td>Soyadınız</td>
            <td>:</td>
            <td><input type="text"></td>
         </tr>
         <tr>
            <td>Mesajınız</td>
            <td>:</td>
            <td><textarea></textarea></td>
         </tr>
         <tr>
            <td colspan="3"><button>Gönder</button></td>
         </tr>
      </tbody>
   </table>
</form>

Ardından bu componentlerin route haritalarını oluşturalım.

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './components/home/home.component';
import { AboutComponent } from './components/about/about.component';
import { ContactComponent } from './components/contact/contact.component';

const routes: Routes = [
  { path: "", redirectTo: "/home", pathMatch: "full" },
  { path: "home", component: HomeComponent },
  { path: "about", component: AboutComponent },
  { path: "contact", component: ContactComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Ve son olarak ana componentte bu componentlerin yönlendirme linklerini oluşturalım.
app.component.html;

<a routerLink="home">Home</a> | <a routerLink="about">About</a> | <a routerLink="contact">Contact</a> <br>
<router-outlet></router-outlet>

Şimdi uygulamayı derleyip yayına alalım ve componentlere yapılan istekler neticesinde kaynak kodunu inceleyelim.
Angular 8'de Angular Universal İle Server-Side Rendering (SSR)

Evet… Görüldüğü üzere Angular uygulamamızda componentlere yapılan istekler neticesinde üretilen component tarayıcıya gönderilmekte lakin kaynak kodu değiştirmemektedir. Haliyle bu durum makalemizin başında da ifade ettiğimiz gibi SEO açısından ciddi manada kayba sebebiyet verecektir. Hal böyleyken gelin Angular Universal İle Server-Side Rendering’in (SSR) uygulamaya nasıl kurulacağını ve gerekli konfügürasyonların ne şekilde yapılacağını inceleyelim.

Uygulamaya Angular Universal’in Kurulması

Angular Universal’i kurabilmek için uygulama dizininde aşağıdaki konsept prototipini çalıştırıyoruz.

ng add @nguniversal/express-engine --clientProject projectName

Yukarıdaki kod bloğunda bulunan konsepte göz atarsanız eğer “projectName” kısmına uygulamanın birebir aynı ismi verilmesi gerekmektedir. Eğer proje adınızı bilmiyorsanız, bunun için “package.json” dosyasında tanımlanmış “name” değerini baz alabilirsiniz.
Angular 8'de Angular Universal İle Server-Side Rendering (SSR)
İlgili kod konseptini çalıştırdıktan sonra yukarıdaki ekran görüntüsünde de vurgulandığı gibi uygulamada “src/main.server.ts”, “src/app/app.server.module.ts”, “tsconfig.server.json”, “webpack.server.config.js” ve “server.ts” dosyaları oluşturulmuştur. Şimdi “app.module.ts” dosyasına göz atalım;
Angular 8'de Angular Universal İle Server-Side Rendering (SSR)
İlgili dosya içerisinde import edilmiş olan “BrowserModule” isimli modül uygulamanın server üzerinden render edilmesini sağlamaktadır. Burada dikkat edilmesi gereken husus “BrowserModule.withServerTransition({ appId: ‘Example’ })” komutundaki “appId” değerinin projenizin ismiyle(package.json : name) aynı olması gerekmektedir.

Bu işlemden sonra aşağıdaki kod konseptiyle uygulamayı derleyip, ayağa kaldıralım.

npm run build:ssr && npm run serve:ssr

Angular 8'de Angular Universal İle Server-Side Rendering (SSR)
Derleme neticesinde bizlere verilmiş olan adresi(“http://localhost:4000”) tarayıcı üzerinden açarak componentlere yapılan istek neticelerinin kaynak kodlarını inceleyelim;
Angular 8'de Angular Universal İle Server-Side Rendering (SSR)
Görüldüğü üzere componentlere yapılan istekler Angular Universal aracılığıyla Server Side Rendering (SS) edilmekte ve tarayıcıya gönderilmektedir.

Sunucu Tarafında Çalışmasını İstemediğimiz Komutları Tarayıcıda Çalıştırmak

Server Side Rendering (SSR) yapılmış bir uygulama yapısal olarak sunucu tarafında işlenmekte ve neticede oluşturulan çıktı tarayıcıya gönderilmektedir. Lakin; “setTimeout” ve “setInterval” gibi bazı yapılar yahut “localStorage” veya “sessionStorage” gibi depolara erişim sağlayan özellikler tarayıcıya bağlı olarak çalışmakta ve tarayıcılar üzerinde işlenmektedirler.

Dolayısıyla bu yapılardan herhangi biri uygulamada kullanılıyorsa SSR sunucu tarafından erişemeyecek ve hata fırlatacaktır.

Örneğin Angular Universal ile ayağa kaldırılan bir uygulamanın herhangi bir noktasında “localStorage” ile işlem yaptığımızı düşünürsek ilgili alana istek geldiği taktirde yukarıdaki bahsettiğimiz duruma istinaden sunucu tarafından ilgili özelliğe erişilemeyecek ve “localStorage is not defined” hatası verilecektir. Tabi bu hata Angular Universal’i kullanmadığımız durumlar için geçerli olmayacaktır.

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
  constructor() { }
  ngOnInit() {
    if (localStorage.getItem("a")) {
      alert(localStorage.getItem("a"))
    }
  }
}

Angular 8'de Angular Universal İle Server-Side Rendering (SSR)

Dolayısıyla böyle bir durumda, sunucuda çalışmayacak olan kodları sade ve sadece tarayıcıda çalıştırması gerektiğine dair mimariye bildirecek bir kontrol oluşturmamız yeterli olacaktır. Bunun için “PLATFORM_ID” değerini ilgili componentin constructerında inject etmemiz ve aşağıdaki gibi kullanmamız gerekmektedir.

import { Component, OnInit, Inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
  constructor(@Inject(PLATFORM_ID) private platformId: Object) { }
  ngOnInit() {
    if (isPlatformBrowser(this.platformId)) {
      if (localStorage.getItem("a")) {
        alert(localStorage.getItem("a"))
      }
    }
  }
}

Yukarıdaki kod bloğunu incelerseniz eğer “@angular/common” dizininde bulunan “isPlatformBrowser” modülü ile sadece tarayıcıda çalışmasını istediğimiz kodları belirleyebilmekteyiz ve bunu gerçekleştirirken inject neticesinde elde ettiğimiz PLATFORM_ID değerini kullanmaktayız. Velhasıl böylece ilgili hatayı düzeltmiş bulunmaktayız.

Ayriyetten “isPlatformBrowser” modülündeki işleve benzer olarak sadece sunucuda çalışmasını istediğimiz kodlarıda kontrollü bir şekilde irademizle belirtebilmek için “isPlatformServer” modülünü kullanabiliriz.

import { Component, OnInit, Inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from "@angular/common";
@Component({
  selector: 'app-contact',
  templateUrl: './contact.component.html',
  styleUrls: ['./contact.component.css']
})
export class ContactComponent implements OnInit {

  constructor(@Inject(PLATFORM_ID) private platformId: Object) { }
  ngOnInit() {
    if (isPlatformBrowser(this.platformId)) {
      //Tarayıcıda çalışacak kodlar...
    }
    if (isPlatformServer(this.platformId)) {
      //Sunucuda çalışacak kodlar...
    }
  }
}

Böylece sunucuda ve tarayıcıda çalışacak kodların net ayrımını hem yazılımsal hemde görsel olarak gerçekleştirmiş olmaktayız.

Ve böylece geliştirilen tüm Angular uygulamalarının içerikleri arama motorları tarafından rahatça okunabilecek ve hedef kullanıcı kitlesine daha rahat erişebilmesini sağlamış olacaktır.

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

*