0
0
Lập trình
Sơn Tùng Lê
Sơn Tùng Lê103931498422911686980

Hướng dẫn Angular 19 SSR trong 10 phút: Tối ưu SEO và tìm kiếm

Đăng vào 2 tuần trước

• 5 phút đọc

Hướng dẫn Angular 19 SSR trong 10 phút: Tối ưu SEO và tìm kiếm

Bạn muốn có thời gian tải trang nhanh, nội dung dễ lập chỉ mục và trải nghiệm tìm kiếm mượt mà mà không cần tái cấu trúc lớn? Hãy tham khảo hướng dẫn từng bước này để thiết lập Angular 19 SSR, thêm dữ liệu SEO theo cấp độ route (với JSON-LD) và tìm kiếm debounced sử dụng Signals.

Chúng ta sẽ giả định bạn đã có một ứng dụng Angular 19 mới. Các đoạn mã sẽ tối giản và hướng tới sản xuất.


1) Thêm Angular Universal (SSR)

Để bắt đầu, bạn cần thêm Angular Universal vào dự án của mình:

bash Copy
ng add @nguniversal/express-engine

Lệnh này sẽ tạo ra một máy chủ Express và cập nhật các mục tiêu build của bạn. Bạn có thể chạy nó bằng cách:

bash Copy
npm run dev:ssr
# hoặc build & serve
npm run build:ssr && npm run serve:ssr

Việc sử dụng SSR giúp cải thiện Core Web Vitals, khả năng lập chỉ mục và các trang đích—đặc biệt cho các sản phẩm SaaS dựa vào lưu lượng truy cập tự nhiên (xem thêm về việc xây dựng ứng dụng SaaS tại đây).


2) Dữ liệu SEO theo cấp độ route + JSON-LD

a) Đặt dữ liệu SEO trên các route

Để thêm dữ liệu SEO, bạn cần chỉnh sửa tệp app.routes.ts như sau:

typescript Copy
// app.routes.ts
export const routes: Routes = [
  {
    path: '',
    loadComponent: () => import('./home.component').then(m => m.HomeComponent),
    data: {
      title: 'Trang Chủ — Nhanh và Dễ Tìm',
      description: 'Nội dung được render từ server với Angular 19 và Signals.',
    },
  },
];

b) Áp dụng Title/Meta khi điều hướng

Trong tệp seo.service.ts, bạn có thể cập nhật tiêu đề và meta cho các route:

typescript Copy
// seo.service.ts
import { Injectable, inject } from '@angular/core';
import { Title, Meta } from '@angular/platform-browser';
import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
import { filter, map, mergeMap } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class SeoService {
  private router = inject(Router);
  private title = inject(Title);
  private meta = inject(Meta);
  private route = inject(ActivatedRoute);

  init() {
    this.router.events.pipe(
      filter(e => e instanceof NavigationEnd),
      map(() => {
        let r = this.route;
        while (r.firstChild) r = r.firstChild;
        return r;
      }),
      mergeMap(r => r.data)
    ).subscribe(d => {
      if (d['title']) this.title.setTitle(d['title']);
      if (d['description']) {
        this.meta.updateTag({ name: 'description', content: d['description'] });
      }
    });
  }
}

Hãy gọi seoService.init() một lần trong constructor của AppComponent.

c) Thêm JSON-LD (thân thiện với SSR)

Bạn có thể tạo một dịch vụ để thêm JSON-LD vào trang của mình:

typescript Copy
// jsonld.service.ts
import { DOCUMENT } from '@angular/common';
import { Injectable, Inject, Renderer2, RendererFactory2 } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class JsonLdService {
  private r: Renderer2;
  constructor(@Inject(DOCUMENT) private doc: Document, rf: RendererFactory2) {
    this.r = rf.createRenderer(null, null);
  }
  set(schema: object, id = 'app-jsonld') {
    const prev = this.doc.getElementById(id);
    if (prev) prev.remove();
    const script = this.r.createElement('script');
    script.type = 'application/ld+json';
    script.id = id;
    script.text = JSON.stringify(schema);
    this.r.appendChild(this.doc.head, script);
  }
}

Ví dụ sử dụng trong một component trang:

typescript Copy
jsonLd.set({
  '@context': 'https://schema.org',
  '@type': 'SoftwareApplication',
  name: 'Ứng dụng Mẫu',
  applicationCategory: 'BusinessApplication'
});

Bạn có cần chuyên môn về SSR cho các trang đích Angular hoặc các module ERP/CRM lớn hơn không? (tham khảo thêm về Angular và ERP/CRM).


3) Tìm kiếm debounced với Signals

Một tìm kiếm nhỏ gọn, an toàn với kiểu dữ liệu mà không làm quá tải API của bạn.

typescript Copy
// search.component.ts
import { Component, signal, computed, effect, inject } from '@angular/core';
import { toObservable } from '@angular/core/rxjs-interop';
import { HttpClient } from '@angular/common/http';
import { debounceTime, distinctUntilChanged, switchMap } from 'rxjs/operators';

@Component({
  standalone: true,
  selector: 'app-search',
  template: `
    <input
      type="search"
      placeholder="Tìm kiếm…"
      [value]="q()"
      (input)="q((($event.target as HTMLInputElement).value || '').trim())" />
    <ul>
      <li *ngFor="let r of results()">{{ r.name }}</li>
    </ul>
  `
})
export class SearchComponent {
  private http = inject(HttpClient);
  q = signal('');
  results = signal<{ name: string }[]>([]);

  // Kết nối Signal -> RxJS để debounce
  constructor() {
    toObservable(this.q).pipe(
      debounceTime(300),
      distinctUntilChanged(),
      switchMap(term =>
        term ? this.http.get<{ name: string }[]>(`/api/search?q=${encodeURIComponent(term)}`) : []
      )
    ).subscribe(data => this.results.set(Array.isArray(data) ? data : []));
  }
}

Điểm cuối Express tối thiểu (Node 18+):

typescript Copy
// server/api.ts
import express from 'express';
const app = express();

app.get('/api/search', (req, res) => {
  const q = String(req.query.q || '').toLowerCase();
  const db = ['Alpha', 'Beta', 'Gamma', 'Angular', 'Signals'].map(name => ({ name }));
  res.json(db.filter(x => x.name.toLowerCase().includes(q)));
});

app.listen(3000);

Mô hình này giữ cho giao diện người dùng phản hồi nhanh, tránh việc lấy dữ liệu quá mức và hoạt động liền mạch với SSR/hydration.


Điều gì tiếp theo?

  • Thêm bộ nhớ cache HTTP (ETag) cho kết quả tìm kiếm.
  • Theo dõi các thuật ngữ tìm kiếm để thông báo quyết định sản phẩm.
  • Nếu ngăn xếp của bạn bao gồm các backend Node.js/Express hoặc tích hợp AI (phân loại, tóm tắt), hãy giữ cho các mối quan tâm tách biệt sau các bộ điều hợp rõ ràng.

Các tài liệu hữu ích (đọc thêm)

  • Tìm hiểu thêm về các nguyên tắc cơ bản của Angular SSR.
  • Hướng dẫn cho các dịch vụ sản xuất Node.js.
  • Xây dựng nền tảng SaaS vững chắc.
  • Khi nào nên thiết kế các module ERP/CRM tùy chỉnh.
  • Xử lý nhu cầu cao với tăng cường nhân lực.
Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào