@omnidyon/ngx-seo-meta
v1.0.0
Published
Lightweight SEO meta management for Angular (standalone & router-aware).
Readme
A lightweight, declarative SEO meta-tag management library for Angular 19+.
It gives you:
- Path-based SEO, driven by a central config.
- Route-driven SEO, driven by
route.data.seo. - Automatic updates on every navigation.
- A single, focused service that applies
<title>and<meta>tags.
Works with standalone apps and classic NgModule setups.
Features
- ✅ Angular 19+ compatible
- ✅ Standalone & NgModule friendly
- ✅ Auto-update SEO on
NavigationEnd - ✅ Two parallel SEO systems:
- Path-based SEO via
SEO_META_CONFIG - Route-based SEO via
route.data.seo
- Path-based SEO via
- ✅ Central
SeoMetaServicewith:updateForPath(path: string)applyRouteMeta(entry: SeoMetaDefinition)
- ✅ Open Graph meta integration (documented via
SeoMetaDefinition) - 🚧 Roadmap for:
- Social meta helpers (Twitter, richer OG)
- JSON-LD helpers
- Canonical + hreflang utilities
- SSR-aware tweaks
- Typed config builder & dev-time diagnostics
Installation
npm install @omnidyon/ngx-seo-metaPeer dependencies: Angular 19+.
Core Concepts
ngx-seo-meta is intentionally simple:
- One service:
SeoMetaService - One config token:
SEO_META_CONFIG - Two ways to supply SEO:
- A central path-based config
- Route metadata using
route.data.seo
The service API is stable and must not be broken:
class SeoMetaService {
updateForPath(path: string): void;
applyRouteMeta(entry: SeoMetaDefinition): void;
}Internally it uses Angular's Title and Meta services and, if available, Router to react to navigation.
SEO Meta Definition
All SEO definitions (config-based or route-based) share the same shape:
export interface SeoMetaDefinition {
title: string;
description: string;
ogTitle?: string;
ogDescription?: string;
ogUrl?: string;
ogImage?: string;
ogType?: string;
ogSiteName?: string;
twitterTitle?: string;
twitterDescription?: string;
twitterImage?: string;
twitterCard?: string;
twitterSite?: string;
twitterCreator?: string;
}The library:
- Always sets
<title>fromtitle. - Updates the
<meta name="description">. - Applies OG fields when present, with sensible fallbacks (e.g.
ogTitle ?? title).
Path-Based SEO (Config-Driven)
This is the original mechanism: map normalized URL paths to SEO definitions.
1. Define the config
// seo-config.ts
import type { SeoMetaConfig } from '@omnidyon/ngx-seo-meta';
export const SEO_CONFIG: SeoMetaConfig = {
'/': {
title: 'Home – My App',
description: 'Welcome to my Angular application.',
ogUrl: 'https://example.com/',
},
'/about': {
title: 'About Us',
description: 'Learn more about the team behind this project.',
},
'/blog': {
title: 'Blog',
description: 'Articles, tutorials and release notes.',
},
};SeoMetaConfig is just:
export type SeoMetaConfig = Record<string, SeoMetaDefinition>;2. Provide the config
Using standalone bootstrap:
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';
import { provideSeoMeta } from '@omnidyon/ngx-seo-meta';
import { SEO_CONFIG } from './seo-config';
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes),
provideSeoMeta(SEO_CONFIG),
],
});3. How it works at runtime
SeoMetaServicelistens toRouter.events(ifRouteris present).- On every
NavigationEnd:- It normalizes the URL:
- strips query & hash
- trims trailing slashes (except
/)
- Looks up the path in
SEO_CONFIG. - Applies the
SeoMetaDefinitionvia the internalapply()method.
- It normalizes the URL:
You can also trigger it manually:
constructor(private readonly seo: SeoMetaService) {}
ngOnInit() {
this.seo.updateForPath('/blog');
}Route-Driven SEO (data.seo)
This is the new mechanism: put SEO definitions directly on your routes.
1. Add seo to your route data
// app.routes.ts
import { Routes } from '@angular/router';
import type { SeoMetaDefinition } from '@omnidyon/ngx-seo-meta';
export const routes: Routes = [
{
path: '',
loadComponent: () => import('./home/home.component'),
data: {
seo: {
title: 'Home – My App',
description: 'Welcome to the homepage.',
} satisfies SeoMetaDefinition,
},
},
{
path: 'blog/:slug',
loadComponent: () => import('./blog/blog.component'),
data: {
seo: (route): SeoMetaDefinition => {
const slug = route.paramMap.get('slug') ?? 'article';
return {
title: `Blog – ${slug}`,
description: `Reading article: ${slug}`,
};
},
},
},
];The seo data supports:
type RouteSeoMeta = SeoMetaDefinition;
type RouteSeoMetaConfig =
| RouteSeoMeta
| ((route: ActivatedRouteSnapshot) => RouteSeoMeta);2. Enable route-driven SEO
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { AppComponent } from './app/app.component';
import { routes } from './app/app.routes';
import { provideSeoMetaRouting } from '@omnidyon/ngx-seo-meta';
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes),
provideSeoMetaRouting(),
],
});3. What provideSeoMetaRouting() does
- Uses
provideEnvironmentInitializer(Angular 16+/19+ style). - On app init:
- Injects
EnvironmentInjector. - Resolves
RouterandSeoMetaService. - Subscribes to
router.events.
- Injects
- On every
NavigationEnd:- Walks to the deepest
ActivatedRouteSnapshot. - Reads
data['seo']. - If present:
- If it's a function: calls it with the snapshot.
- If it's an object: uses it directly.
- Calls
seo.applyRouteMeta(definition).
- Walks to the deepest
4. Coexisting with path-based SEO
You can use both providers together:
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes),
provideSeoMeta(SEO_CONFIG), // path-based
provideSeoMetaRouting(), // route-based
],
});They do not override each other:
- Path-based SEO still responds to
updateForPath()and config entries. - Route-based SEO calls
applyRouteMeta()with route-specific metadata.
Manual Usage
You can also use the service directly if you need full control.
Imperative route-style application
constructor(private readonly seo: SeoMetaService) {}
setDynamicSeoForProduct(product: Product) {
this.seo.applyRouteMeta({
title: `Buy ${product.name} – My Store`,
description: product.shortDescription,
ogUrl: `https://example.com/products/${product.slug}`,
});
}Imperative path-based lookup
constructor(private readonly seo: SeoMetaService) {}
ngOnInit() {
// Forces the service to use the config entry for /special
this.seo.updateForPath('/special');
}Angular 19+ & Standalone
This library is designed with Angular 19+ and standalone APIs in mind:
- Uses
provideEnvironmentInitializer. - Plays well with
bootstrapApplication. - Still compatible with NgModule apps via a simple module wrapper if needed.
Example (NgModule-style):
import { NgModule } from '@angular/core';
import { BrowserModule, bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { provideSeoMeta, provideSeoMetaRouting } from '@omnidyon/ngx-seo-meta';
@NgModule({
imports: [BrowserModule],
bootstrap: [AppComponent],
providers: [
provideSeoMeta(SEO_CONFIG),
provideSeoMetaRouting(),
],
})
export class AppModule {}Testing
The library is tested with Vitest, but:
- We do not mock
@angular/coreinternals. SeoMetaServiceis tested in isolation with spies forTitleandMeta.- Providers (
provideSeoMeta,provideSeoMetaRouting) are tested via Angular'sTestBedand simple fakes (forRouteretc.) where needed.
If you’re testing in your own app:
- Use
TestBed.configureTestingModule. - Provide
SeoMetaService,Title,Meta, and a fake or realRouterdepending on the scenario. - Avoid mocking Angular’s core module directly.
Roadmap
Planned / in-progress features:
- Social meta helper
- Better ergonomics for OG + Twitter tags.
- JSON-LD helper
- Quick helpers for common schemas (e.g. Article, BreadcrumbList).
- Canonical + hreflang utilities
- Guards / directives
- Small helpers to glue SEO configuration to templates & guards.
- Environment / SSR-aware behavior
- Avoid touching
documenton the server.
- Avoid touching
- Dev-time diagnostics
- Warnings for missing descriptions, duplicate titles, etc.
- Typed configuration & builder
- Fluent, fully typed config building API.
License
MIT. Use it, fork it, break it, fix it.
Credits
Built for people who are tired of rewriting the same <title> and <meta> boilerplate across Angular apps.
