Schema.org

This commit is contained in:
2026-02-05 12:49:09 +01:00
parent 70a50e0ff6
commit 6f1109d593
4 changed files with 122 additions and 49 deletions

View File

@@ -1,5 +1,5 @@
import { Injectable, inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import { Injectable, inject, PLATFORM_ID, Renderer2, RendererFactory2 } from '@angular/core';
import { isPlatformBrowser, DOCUMENT } from '@angular/common';
import { Meta, Title } from '@angular/platform-browser';
import { Router } from '@angular/router';
@@ -22,11 +22,17 @@ export class SeoService {
private router = inject(Router);
private platformId = inject(PLATFORM_ID);
private isBrowser = isPlatformBrowser(this.platformId);
private document = inject(DOCUMENT);
private renderer: Renderer2;
private readonly defaultImage = 'https://www.bizmatch.net/assets/images/bizmatch-og-image.jpg';
private readonly siteName = 'BizMatch';
private readonly baseUrl = 'https://www.bizmatch.net';
constructor(rendererFactory: RendererFactory2) {
this.renderer = rendererFactory.createRenderer(null, null);
}
/**
* Get the base URL for SEO purposes
*/
@@ -109,20 +115,18 @@ export class SeoService {
}
/**
* Update canonical URL
* Update canonical URL (SSR-compatible using Renderer2)
*/
private updateCanonicalUrl(url: string): void {
if (!this.isBrowser) return;
let link: HTMLLinkElement | null = document.querySelector('link[rel="canonical"]');
let link: HTMLLinkElement | null = this.document.querySelector('link[rel="canonical"]');
if (link) {
link.setAttribute('href', url);
this.renderer.setAttribute(link, 'href', url);
} else {
link = document.createElement('link');
link.setAttribute('rel', 'canonical');
link.setAttribute('href', url);
document.head.appendChild(link);
link = this.renderer.createElement('link');
this.renderer.setAttribute(link, 'rel', 'canonical');
this.renderer.setAttribute(link, 'href', url);
this.renderer.appendChild(this.document.head, link);
}
}
@@ -269,32 +273,40 @@ export class SeoService {
}
/**
* Inject JSON-LD structured data into page
* Inject JSON-LD structured data into page (SSR-compatible using Renderer2)
*/
injectStructuredData(schema: object): void {
if (!this.isBrowser) return;
// Clear existing schema scripts with the same type
this.removeAllSchemas();
// Remove existing schema script
const existingScript = document.querySelector('script[type="application/ld+json"]');
if (existingScript) {
existingScript.remove();
}
// Create new script element using Renderer2 (works in both SSR and browser)
const script = this.renderer.createElement('script');
this.renderer.setAttribute(script, 'type', 'application/ld+json');
this.renderer.setAttribute(script, 'data-schema', 'true');
// Add new schema script
const script = document.createElement('script');
script.type = 'application/ld+json';
script.text = JSON.stringify(schema);
document.head.appendChild(script);
// Create text node with schema JSON
const schemaText = this.renderer.createText(JSON.stringify(schema));
this.renderer.appendChild(script, schemaText);
// Append to document head
this.renderer.appendChild(this.document.head, script);
}
/**
* Clear all structured data
* Remove all schema scripts (internal helper, SSR-compatible)
*/
private removeAllSchemas(): void {
const existingScripts = this.document.querySelectorAll('script[data-schema="true"]');
existingScripts.forEach(script => {
this.renderer.removeChild(this.document.head, script);
});
}
/**
* Clear all structured data (SSR-compatible)
*/
clearStructuredData(): void {
if (!this.isBrowser) return;
const scripts = document.querySelectorAll('script[type="application/ld+json"]');
scripts.forEach(script => script.remove());
this.removeAllSchemas();
}
/**
@@ -516,20 +528,21 @@ export class SeoService {
}
/**
* Inject multiple structured data schemas
* Inject multiple structured data schemas (SSR-compatible using Renderer2)
*/
injectMultipleSchemas(schemas: object[]): void {
if (!this.isBrowser) return;
// Clear existing schema scripts
this.removeAllSchemas();
// Remove existing schema scripts
this.clearStructuredData();
// Add new schema scripts
// Add new schema scripts using Renderer2
schemas.forEach(schema => {
const script = document.createElement('script');
script.type = 'application/ld+json';
script.text = JSON.stringify(schema);
document.head.appendChild(script);
const script = this.renderer.createElement('script');
this.renderer.setAttribute(script, 'type', 'application/ld+json');
this.renderer.setAttribute(script, 'data-schema', 'true');
const schemaText = this.renderer.createText(JSON.stringify(schema));
this.renderer.appendChild(script, schemaText);
this.renderer.appendChild(this.document.head, script);
});
}

View File

@@ -1,12 +1,25 @@
import { environment } from '../../environments/environment';
// Development: use browser-bunyan for rich logging
// Production: use lightweight console wrapper to avoid loading 50KB+ library
export const createLogger = environment.production
? (name: string) => ({
info: (...args: any[]) => console.log(`[${name}]`, ...args),
warn: (...args: any[]) => console.warn(`[${name}]`, ...args),
error: (...args: any[]) => console.error(`[${name}]`, ...args),
debug: () => {}, // no-op in production
})
: require('browser-bunyan').createLogger;
// Lightweight logger implementation for both dev and production
// Avoids dynamic require() which causes build issues
const createLoggerImpl = (name: string) => ({
info: (...args: any[]) => {
if (!environment.production) {
console.log(`[${name}]`, ...args);
}
},
warn: (...args: any[]) => console.warn(`[${name}]`, ...args),
error: (...args: any[]) => console.error(`[${name}]`, ...args),
debug: (...args: any[]) => {
if (!environment.production) {
console.debug(`[${name}]`, ...args);
}
},
trace: (...args: any[]) => {
if (!environment.production) {
console.trace(`[${name}]`, ...args);
}
}
});
export const createLogger = createLoggerImpl;