এঙ্গুলার সার্ভার সাইড রেন্ডারিং - এঙ্গুলার উনিভার্সাল

angular

সার্ভার সাইড রেন্ডারিং কি?


এঙ্গুলার মূলত ক্লায়েন্ট সাইড জাভাস্ক্রিপ্ট ফ্রেমওয়ার্ক হিসাবে যাত্রা শুরু করেছিল। কিন্তু ক্লায়েন্ট সাইড ফ্রেমওয়ার্ক হবার কারণে অনেক সুবিধার মাঝেও কিছু অসুবিধা থেকেই যাচ্ছিলো। যেমন,

  • লো কনফিগারেশনের মোবাইলে বা কম্পিউটারে ক্লায়েন্ট সাইড এপ্লিকেশন চলতে সমস্যায় পড়তে হয়
  • পেজ লোড হতে বেশি সময় লাগে। যেহেতু ক্লায়েন্টসাইড অপ্প্লিকেশন, তাই প্রথমে অনেক স্ক্রিপ্ট লোড নিতে হয়
  • SEO বা সার্চ ইঞ্জিন অপটিমাইজেশন এর জন্য অনেক ক্রলার ক্লায়েন্ট সাইড এপ্লিকেশন পড়তে পারে না




তাহলে সমাধান কি?


আমি এখন পর্যন্ত তিনটা সমাধান পেয়েছি।

১. যে সব পেজ পাবলিক বা সবাই দেখতে পারে, সে সব পেজের জন্য আলাদা ভাবে সার্ভার সাইড টেমপ্লেট ইঞ্জিন দ্বারা HTML জেনারেট করে ফেলা। যেমন, স্প্রিং জাভার ক্ষেত্রে FreeMarker বা Thymeleaf ব্যবহার করা। আর যে পেজ গুলা মূলত অ্যাডমিন, সেগুলার জন্য আলাদা ক্লায়েন্ট সাইড আপা বানানো। অসুবিধা হচ্ছে, দুই রকম ক্লায়েন্ট এপ্লিকেশন হয়ে যাচ্ছে। যার ফলে দুই রকম ডেপ্লয়মেন্ট, দুইটা টেকনোলজি মেইনটেইন করা লাগে।


২. এডমিন এপ ক্লায়েন্ট সাইড ফ্রেমওয়ার্ক দিয়ে বানানো এবং পাবলিক পেজ নোড জেএস দিয়ে বানানো। এখনো দুইটা আলাদা অপ্প্লিকেশন হলেও সুবিধা হচ্ছে টেকনোলোজি খুব কাছাকাছি বা একই রকম।


৩. এঙ্গুলার ৪ থেকে সার্ভার সাইড রেন্ডারিং বা ssr নিয়ে আসা হয়েছে।তারমানে হচ্ছে একই কোড বেস, একই সাথে ক্লায়েন্ট অপ্প্লিকেশন এবং দরকার হলে সার্ভার সাইড থেকে কোড জেনারেট করে আনা যায়। আমার মতে এটাই এখন পর্যন্ত সবচেয়ে ভালো সমাধান। আমি আমার ব্লগে এই সিস্টেম ব্যবহার করেছি। এটা আপনার এঙ্গুলার >=৪ ভার্সনের উপর সহজেই বসিয়ে দেয়া যেতে পারে। এই প্রজেক্টের নাম হচ্ছে Angular Universal



সাধারণ এঙ্গুলার এপকে সার্ভার সাইড এপে কনভার্ট করা


১. আপনার ক্লায়েন্ট প্রজেক্টে nguniversal লাইব্রেরি যুক্ত করতে হবে

ng add @nguniversal/express-engine --clientProject <বর্তমান_প্রজেক্টের_নাম> 


২. যদি পিউর ক্লায়েন্ট ফাংশনালিটি ব্যবহার করে থাকেন(যেমন, localStorage, document, window) তাহলে, সে গুলাকে আলাদা ভাবে ব্যবহার করতে হবে। সবচেয়ে সহজ হচ্ছে PLATFORM_ID ব্যবহার করে জেনে নেয়া এখন কোড ব্রাউজারে নাকি সার্ভারে ব্যবহার হচ্ছে। এর জন্য প্রথমে কন্সট্রাক্টরে প্লাটফর্ম আইডি ইনজেক্ট করতে হবে। নিচে AuthService এ আমি যেভাবে PLATFORM_ID ব্যবহার করেছি


@Injectable()
export class AuthService {
    constructor(
        @Inject(PLATFORM_ID) private platformId,) {
    }


তারপর কন্ডিশন চেক করে কাজ করতে হবে

if (isPlatformBrowser(this.platformId)) {
    return localStorage;
} else {
  // ভিন্ন ভাবে হ্যান্ডল করতে হবে 
}


৩. যদি ক্লায়েন্ট থেকে API কল করেন, তাহলে সেই এপিআই দুইবার কল হবে। সেটাকে ঠিক করার জন্য TransferState ব্যবহার করতে হয়। প্রথম সার্ভার এবং ক্লায়েন্ট module.ts এর মধ্যে ServerTransferStateModule যুক্ত করে ফেলুন।


@NgModule({
  imports: [
    AppModule,
    ServerModule,
    ModuleMapLoaderModule,
    ServerTransferStateModule,
  ],
  bootstrap: [AppComponent],
})
export class AppServerModule {}


@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'serverApp' }),
    FormsModule,
    HttpClientModule,
    AppRoutingModule,
    LayoutModule,
    BrowserTransferStateModule,
  ],
  providers: [   {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    },
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }


তারপর একটা Http Interceptor লিখতে হবে এবং এই ইন্টারসেপ্টরটাকে app.module.ts এ যুক্ত করে দিতে হবে।


ইন্টারসেপ্টর

@Injectable()
export class HttpInterceptorService implements HttpInterceptor {

    constructor(private transferState: TransferState,
                @Inject(PLATFORM_ID) private platformId: any) {}

    public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

        if (request.method !== 'GET') {
            return next.handle(request);
        }

        const key: StateKey<string> = makeStateKey<string>(request.url);

        if (isPlatformServer(this.platformId)) {
            return next.handle(request).pipe(tap((event) => {
                this.transferState.set(key, ( ( event as HttpResponse<any>).body));
            }));
        } else {
            const storedResponse = this.transferState.get<any>(key, null);
            if (storedResponse) {
                const response = new HttpResponse({body: storedResponse, status: 200});
                this.transferState.remove(key);
                return of(response);
            } else {
                return next.handle(request);
            }
        }
    }
}


এপ মডিউল

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'serverApp' }),
    FormsModule,
    HttpClientModule,
    AppRoutingModule,
    LayoutModule,
    BrowserTransferStateModule,
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: HttpInterceptorService,
      multi: true
    },
    {
      provide: HTTP_INTERCEPTORS,
      useClass: AuthInterceptor,
      multi: true
    },
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }


ব্যাস আপনার ক্লায়েন্ট সাইড এপ এখন সার্ভার সাইড হিসাবে কাজ করবে। সার্ভার রান করার জন্য নিচের কমান্ড চালান

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

এখন একটা এক্সপ্রেস ফ্রেমওয়ার্ক নোড জে এস এর উপর চলবে এবং আপনার কোড সার্ভার সাইড থেকে HTML আকারে জেনারেট হয়ে আসবে।


এখন, আপনি সহজেই SEO করতে পারবেন, সোশ্যাল শেয়ারিং করতে পারবেন এবং লো কনফিগারের ডিভাইসে আপনার পেজ ভালো ভাবেই কম সময়ে লোড হয়ে যাবে। ধন্যবাদ।