메타데이터가 왜 중요한가?
SPA(싱글 페이지 앱)라고 해도 결국 브라우저 탭 제목, 검색엔진, SNS(카톡/슬랙/트위터 카드)에서 보이는 정보는 HTML <head>의 메타데이터에 의존합니다.
대표적인 것들
- <title> — 브라우저 탭 제목, SEO 기본
- <meta name="description"> — 검색 결과 요약 문구
- <meta property="og:title">, <meta property="og:description">, <meta property="og:image"> — SNS 링크 미리보기 (Open Graph)
Next.js App Router의 generateMetadata()처럼 페이지별로 일관되게 메타데이터를 관리하고 싶다면, Angular에서는 다음 3가지를 조합해서 쓰게 됩니다.
- Title 서비스 (@angular/platform-browser)
- Meta 서비스 (@angular/platform-browser)
- Angular Router의 Route.title + TitleStrategy + ResolveFn
Angular에서 메타데이터를 다루는 기본 도구
Title 서비스
import { Title } from '@angular/platform-browser';
const title = inject(Title);
title.setTitle('홈 | MyApp');
- 브라우저 탭에 보이는 <title>을 제어합니다.
- 단순 문자열만 다룹니다.
Meta 서비스
import { Meta } from '@angular/platform-browser';
const meta = inject(Meta);
meta.updateTag({ name: 'description', content: '메타 설명입니다.' });
meta.updateTag({ property: 'og:title', content: 'OG 타이틀' });
meta.updateTag({ property: 'og:image', content: 'https://.../image.png' });
- <meta> 태그를 추가/수정/삭제할 수 있습니다.
- name / property 중 하나를 사용합니다.
이 두 가지를 매번 컴포넌트에서 직접 호출해도 되지만, 규모가 커지면 중복과 누락이 생기기 쉽습니다. 그래서 아래처럼 레이어를 나눠서 관리하는 게 좋습니다.
컴포넌트에서 바로 inject()로 제어
먼저, 기본 사용법부터 짚고 넘어가겠습니다.
Standalone 컴포넌트에서 메타데이터 설정
// home-page.component.ts
import { Component, OnInit, inject } from '@angular/core';
import { Title, Meta } from '@angular/platform-browser';
@Component({
standalone: true,
selector: 'app-home-page',
template: `<h1>홈</h1>`,
})
export class HomePageComponent implements OnInit {
private readonly title = inject(Title);
private readonly meta = inject(Meta);
ngOnInit(): void {
this.title.setTitle('홈 | MyApp');
this.meta.updateTag({
name: 'description',
content: 'MyApp 홈 화면입니다. 오늘의 추천 콘텐츠를 확인해 보세요.',
});
this.meta.updateTag({
property: 'og:title',
content: '홈 | MyApp',
});
this.meta.updateTag({
property: 'og:description',
content: 'MyApp 홈 화면입니다. 오늘의 추천 콘텐츠를 확인해 보세요.',
});
}
}
- 작은 앱이나 정적인 페이지 몇 개만 있는 경우에는 이 정도만 해도 충분합니다.
하지만
- 페이지가 많아질수록 ngOnInit 안이 메타 코드로 더러워지고,
- 타이틀 접미사(| MyApp)를 바꾸고 싶을 때 전부 찾아서 수정해야 하고,
- i18n(다국어)이 섞이면 더 복잡해집니다.
그래서 라우터 레벨에서 전역 전략 + 라우트 설정으로 제어하는 패턴으로 올라가 봅니다.
Route.title + TitleStrategy
Angular 14+부터 라우트에 title 속성을 정의하면, 네비게이션 후 자동으로 <title>을 업데이트해 주는 기능이 들어가 있습니다.
라우트에서 정적 타이틀 설정
// app.routes.ts
import { Routes } from '@angular/router';
import { HomePageComponent } from './pages/home-page.component';
import { PostListPageComponent } from './pages/post-list-page.component';
export const routes: Routes = [
{
path: '',
component: HomePageComponent,
title: '홈',
},
{
path: 'posts',
component: PostListPageComponent,
title: '게시글 목록',
},
];
- 그리고 app.config.ts에서 라우터를 제공하면, 기본 TitleStrategy가 이 title을 사용해 <title>을 설정합니다.
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
// ... 다른 provider들
],
};
- 이렇게만 해도 각 페이지마다 다른 <title>이 자동으로 적용됩니다.
- 하지만 여기서도 아쉬운 점이 있죠.
- 모든 라우트의 타이틀에 | MyApp 접미사를 붙이고 싶다거나
- 라우트마다 기본 prefix/suffix 규칙을 통일하고 싶을 때
이때 쓰는 게 커스텀 TitleStrategy입니다.
커스텀 TitleStrategy
// app-title.strategy.ts
import { Injectable, inject } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { RouterStateSnapshot, TitleStrategy } from '@angular/router';
@Injectable({ providedIn: 'root' })
export class AppTitleStrategy extends TitleStrategy {
private readonly title = inject(Title);
private readonly APP_NAME = 'MyApp';
override updateTitle(routerState: RouterStateSnapshot): void {
// 라우트 설정의 title(문자열 또는 Resolver 결과)을 조합해서 title 문자열 생성
const resolvedTitle = this.buildTitle(routerState);
const fullTitle = resolvedTitle
? `${resolvedTitle} | ${this.APP_NAME}`
: this.APP_NAME;
this.title.setTitle(fullTitle);
}
}
- 그리고 app.config.ts에 전략을 등록합니다.
// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter, TitleStrategy } from '@angular/router';
import { routes } from './app.routes';
import { AppTitleStrategy } from './app-title.strategy';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
{ provide: TitleStrategy, useClass: AppTitleStrategy },
],
};
이제부터는 라우트에 순수한 페이지 타이틀만 적어주면:
// app.routes.ts
export const routes: Routes = [
{
path: '',
component: HomePageComponent,
title: '홈',
},
{
path: 'posts',
component: PostListPageComponent,
title: '게시글 목록',
},
];
실제 <title>은:
- / → 홈 | MyApp
- /posts → 게시글 목록 | MyApp
처럼 자동으로 만들어집니다.
'개발 공부 > Angular' 카테고리의 다른 글
| Angular validationForm (0) | 2025.12.14 |
|---|---|
| 앵귤러 메타데이터 관리 (2) (0) | 2025.12.02 |
| Angular inject() (0) | 2025.09.01 |
| Signals vs RxJS (0) | 2025.08.23 |
| Angular에서 signal을 써야 하는 이유 (1) | 2025.08.15 |