From 6f1109d593d7cfc709a9bc7930a2a661db7de6a0 Mon Sep 17 00:00:00 2001 From: Timo Knuth Date: Thu, 5 Feb 2026 12:49:09 +0100 Subject: [PATCH] Schema.org --- bizmatch/src/app/services/seo.service.ts | 89 ++++++++++++++---------- bizmatch/src/app/utils/logger.ts | 33 ++++++--- bizmatch/src/build.ts | 2 +- bizmatch/src/index.html | 47 +++++++++++++ 4 files changed, 122 insertions(+), 49 deletions(-) diff --git a/bizmatch/src/app/services/seo.service.ts b/bizmatch/src/app/services/seo.service.ts index 31f92f4..008ea72 100644 --- a/bizmatch/src/app/services/seo.service.ts +++ b/bizmatch/src/app/services/seo.service.ts @@ -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); }); } diff --git a/bizmatch/src/app/utils/logger.ts b/bizmatch/src/app/utils/logger.ts index f6b2ccd..e6402e4 100644 --- a/bizmatch/src/app/utils/logger.ts +++ b/bizmatch/src/app/utils/logger.ts @@ -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; diff --git a/bizmatch/src/build.ts b/bizmatch/src/build.ts index 4c5c8fc..1a0975a 100644 --- a/bizmatch/src/build.ts +++ b/bizmatch/src/build.ts @@ -1,6 +1,6 @@ // Build information, automatically generated by `the_build_script` :zwinkern: const build = { - timestamp: "GER: 04.02.2026 21:20 | TX: 02/04/2026 2:20 PM" + timestamp: "GER: 05.02.2026 12:45 | TX: 02/05/2026 5:45 AM" }; export default build; \ No newline at end of file diff --git a/bizmatch/src/index.html b/bizmatch/src/index.html index cbae496..263354f 100644 --- a/bizmatch/src/index.html +++ b/bizmatch/src/index.html @@ -61,6 +61,53 @@ + + + + +