diff --git a/src/app/data/index.ts b/src/app/data/index.ts
index c3f79f5..a861a2f 100644
--- a/src/app/data/index.ts
+++ b/src/app/data/index.ts
@@ -1 +1,2 @@
export * from './glossary.data';
+export * from './seo.data';
diff --git a/src/app/data/seo.data.ts b/src/app/data/seo.data.ts
new file mode 100644
index 0000000..c4560d0
--- /dev/null
+++ b/src/app/data/seo.data.ts
@@ -0,0 +1,319 @@
+export const SITE_ORIGIN = 'https://ditectrev.com';
+export const SITE_NAME = 'Ditectrev';
+export const DEFAULT_OG_IMAGE = `${SITE_ORIGIN}/assets/icons/icon-512x512.png`;
+export const DEFAULT_OG_IMAGE_ALT = "Ditectrev's logo";
+export const LLMS_TXT_URL = `${SITE_ORIGIN}/llms.txt`;
+export const INDEX_ROBOTS =
+ 'index,follow,max-snippet:-1,max-image-preview:large,max-video-preview:-1';
+export const NOINDEX_ROBOTS = 'noindex, follow';
+
+export const INDEXABLE_PATHS: readonly string[] = [
+ '/',
+ '/about-us',
+ '/contact',
+ '/methodology',
+ '/services',
+ '/faq',
+ '/glossary',
+ '/sitemap',
+ '/copyrights',
+ '/terms-of-use',
+ '/privacy-and-security',
+ '/partnerships',
+ '/services/cyber-security',
+ '/services/digital-strategy',
+ '/services/software-development',
+];
+
+export type PageJsonLdType =
+ | 'WebSite'
+ | 'AboutPage'
+ | 'ContactPage'
+ | 'FAQPage'
+ | 'Service'
+ | 'WebPage';
+
+export interface SeoBreadcrumb {
+ name: string;
+ path: string;
+}
+
+export interface PageSeo {
+ title: string;
+ description: string;
+ robots?: string;
+ jsonLdType: PageJsonLdType;
+ breadcrumbs: SeoBreadcrumb[];
+ serviceName?: string;
+}
+
+const homeCrumb: SeoBreadcrumb = { name: 'Home', path: '/' };
+const servicesCrumb: SeoBreadcrumb = { name: 'Services', path: '/services' };
+
+export const PAGE_SEO: Record
= {
+ '/': {
+ title: 'Ditectrev – Online Education and IT Consulting',
+ description:
+ 'Information Technology (IT) services company focused on consulting and online education in cyber security, digital strategy, and software development.',
+ jsonLdType: 'WebSite',
+ breadcrumbs: [homeCrumb],
+ },
+ '/about-us': {
+ title: 'About Us | Ditectrev',
+ description:
+ 'Your digital transformation and innovation partner. Creativity and a privacy-preserving approach in cyber security, digital strategy, and software development.',
+ jsonLdType: 'AboutPage',
+ breadcrumbs: [homeCrumb, { name: 'About us', path: '/about-us' }],
+ },
+ '/contact': {
+ title: 'Contact | Ditectrev',
+ description:
+ 'We are partners, not contractors. Contact Ditectrev about online education and IT consulting.',
+ jsonLdType: 'ContactPage',
+ breadcrumbs: [homeCrumb, { name: 'Contact', path: '/contact' }],
+ },
+ '/methodology': {
+ title: 'Methodology | Ditectrev',
+ description:
+ 'Modern delivery approach. Ditectrev works with Scrum, delivering small parts often and involving you at every project stage.',
+ jsonLdType: 'WebPage',
+ breadcrumbs: [homeCrumb, { name: 'Methodology', path: '/methodology' }],
+ },
+ '/services': {
+ title: 'Services | Ditectrev',
+ description:
+ 'Ditectrev services in cyber security, digital strategy, and software development, from data-driven software to end-to-end solutions.',
+ jsonLdType: 'WebPage',
+ breadcrumbs: [homeCrumb, servicesCrumb],
+ },
+ '/faq': {
+ title: 'FAQ | Ditectrev',
+ description:
+ "Answers to common questions about Ditectrev's company, cooperation, education, and IT services.",
+ jsonLdType: 'FAQPage',
+ breadcrumbs: [homeCrumb, { name: 'FAQ', path: '/faq' }],
+ },
+ '/glossary': {
+ title: 'Glossary | Ditectrev',
+ description:
+ "Tables of terms used in Ditectrev's cyber security, digital strategy, and software development services.",
+ jsonLdType: 'WebPage',
+ breadcrumbs: [homeCrumb, { name: 'Glossary', path: '/glossary' }],
+ },
+ '/sitemap': {
+ title: 'Sitemap | Ditectrev',
+ description:
+ 'A directory of pages on ditectrev.com, including company, services, information, and resources.',
+ jsonLdType: 'WebPage',
+ breadcrumbs: [homeCrumb, { name: 'Sitemap', path: '/sitemap' }],
+ },
+ '/copyrights': {
+ title: 'Copyrights | Ditectrev',
+ description:
+ 'Copyright information for Ditectrev content, including what you may and may not copy from our website and assets.',
+ jsonLdType: 'WebPage',
+ breadcrumbs: [homeCrumb, { name: 'Copyrights', path: '/copyrights' }],
+ },
+ '/terms-of-use': {
+ title: 'Terms of Use | Ditectrev',
+ description:
+ "Essential terms that apply to using Ditectrev's website and assets.",
+ jsonLdType: 'WebPage',
+ breadcrumbs: [homeCrumb, { name: 'Terms of Use', path: '/terms-of-use' }],
+ },
+ '/privacy-and-security': {
+ title: 'Privacy & Security | Ditectrev',
+ description:
+ 'How Ditectrev protects your privacy and uses personal data when you use our website.',
+ jsonLdType: 'WebPage',
+ breadcrumbs: [
+ homeCrumb,
+ { name: 'Privacy & Security', path: '/privacy-and-security' },
+ ],
+ },
+ '/partnerships': {
+ title: 'Partnerships | Ditectrev',
+ description:
+ 'Partner with Ditectrev as a creative agency, freelancer, or software house.',
+ jsonLdType: 'WebPage',
+ breadcrumbs: [homeCrumb, { name: 'Partnerships', path: '/partnerships' }],
+ },
+ '/services/cyber-security': {
+ title: 'Cyber Security Services | Ditectrev',
+ description:
+ 'Data-driven and future-proof software together with cyber security: compliance audits, ethical hacking, secure programming, and more.',
+ jsonLdType: 'Service',
+ serviceName: 'Cyber Security',
+ breadcrumbs: [
+ homeCrumb,
+ servicesCrumb,
+ { name: 'Cyber Security', path: '/services/cyber-security' },
+ ],
+ },
+ '/services/digital-strategy': {
+ title: 'Digital Strategy Services | Ditectrev',
+ description:
+ 'Best-in-class development with frictionless digital experience: business development, digital marketing, product design, and more.',
+ jsonLdType: 'Service',
+ serviceName: 'Digital Strategy',
+ breadcrumbs: [
+ homeCrumb,
+ servicesCrumb,
+ { name: 'Digital Strategy', path: '/services/digital-strategy' },
+ ],
+ },
+ '/services/software-development': {
+ title: 'Software Development Services | Ditectrev',
+ description:
+ 'End-to-end solutions with scaled architecture and smooth development: web, mobile, cloud, eCommerce, and more.',
+ jsonLdType: 'Service',
+ serviceName: 'Software Development',
+ breadcrumbs: [
+ homeCrumb,
+ servicesCrumb,
+ { name: 'Software Development', path: '/services/software-development' },
+ ],
+ },
+};
+
+export const NOT_FOUND_SEO: PageSeo = {
+ title: 'Page Not Found | Ditectrev',
+ description:
+ 'The page you requested was not found. Return to the Ditectrev homepage to continue.',
+ robots: NOINDEX_ROBOTS,
+ jsonLdType: 'WebPage',
+ breadcrumbs: [homeCrumb, { name: 'Page not found', path: '' }],
+};
+
+/** Entity-defining Q&A copied from the FAQ page for homepage JSON-LD. */
+export const ENTITY_FAQS: readonly { question: string; answer: string }[] = [
+ {
+ question: 'What is Ditectrev?',
+ answer:
+ 'We are IT company providing online education content and IT Consulting.',
+ },
+ {
+ question: 'What are you doing?',
+ answer:
+ 'We provide online education supported by our blog and three categories of services, i.e. Cyber Security, Digital Strategy and Software Development. Our blog and educational content is related to these three types of services.',
+ },
+ {
+ question: 'Where is the company located?',
+ answer:
+ 'We are working fully remotely from different countries and the company is registered in Poland.',
+ },
+ {
+ question: 'How are you working?',
+ answer:
+ 'We are working using agile technique called Scrum. This is a process for managing projects which relies on frequent updates of a client and delivering small parts, but often during project ongoing.',
+ },
+];
+
+export const SERVICE_OFFERINGS: Record = {
+ '/services/cyber-security': [
+ 'Compliance Audits',
+ 'Cyber Research',
+ 'Ethical Hacking',
+ 'Fuzzing',
+ 'Post-Incident Analysis',
+ 'Proactive Cyber Defence',
+ 'Reverse Engineering',
+ 'Secure Programming',
+ 'Security Analysis',
+ 'Systems Security',
+ 'Social Engineering',
+ 'Source Code Audits',
+ ],
+ '/services/digital-strategy': [
+ 'Business Development',
+ 'Competitors Analysis',
+ 'Crowdfunding & Crowdsourcing',
+ 'Culture Design',
+ 'Data Analysis',
+ 'Digital Marketing',
+ 'Foresight & Trends',
+ 'Innovation Factory',
+ 'Interactive Systems',
+ 'Lean Innovation',
+ "Minimum Viable Products (MVP's) & Proof of Concepts (PoF's)",
+ 'Product Design',
+ ],
+ '/services/software-development': [
+ 'Business Integrations',
+ 'Cloud Solutions',
+ 'Code Reviews',
+ 'Content Management Systems',
+ 'Complex Services',
+ 'Custom Implementations',
+ 'Electronic Commerce (eCommerce)',
+ 'Mobile Development',
+ 'Progressive Web Applications',
+ 'Research & Discovery',
+ 'Software Quality Engineering',
+ 'Web Development',
+ ],
+};
+
+export const METHODOLOGY_STEPS: readonly { name: string; text: string }[] = [
+ {
+ name: 'Business Analysis',
+ text:
+ 'Stage of listening and asking question. Learn about your project, try to understand its business needs and ask questions. Find your differentiators and unique value proposition.',
+ },
+ {
+ name: 'Research',
+ text:
+ 'Stage of researching business and technical capabilities. Investigate your business needs and combine them with a technical realities. Find a golden ratio for business needs and technical realities. Provide you a feedback what we can do. Estimate range of the project in terms of budget and deadline.',
+ },
+ {
+ name: 'Technical Assessment',
+ text:
+ 'Stage of evaluation technical possibilities with certain budget. Determine the budget. Analysis what kind of technologies and tools to use. Propose a solution according to your needs and budget.',
+ },
+ {
+ name: 'Realization',
+ text:
+ 'Stage of execution for the project. Start with small part of the project. Finish the small part of the project. Apply your remarks. Iterate these steps for yet another parts of the project.',
+ },
+ {
+ name: 'Further Cooperation',
+ text:
+ 'Stage of long term partnership. Support you with what we delivered. Further improvements.',
+ },
+];
+
+export const ORGANIZATION_SAME_AS: readonly string[] = [
+ 'https://discord.com/invite/RFjtXKfJy3',
+ 'https://www.facebook.com/ditectrev',
+ 'https://github.com/ditectrev',
+ 'https://www.instagram.com/ditectrev',
+ 'https://www.linkedin.com/company/ditectrev',
+ 'https://medium.com/@ditectrev',
+ 'https://x.com/ditectrev',
+ 'https://www.youtube.com/@Ditectrev',
+];
+
+export function canonicalizePath(rawPath: string): string {
+ const withoutQuery = rawPath.split('?')[0].split('#')[0];
+ if (!withoutQuery || withoutQuery === '/') {
+ return '/';
+ }
+ return withoutQuery.length > 1 && withoutQuery.endsWith('/')
+ ? withoutQuery.slice(0, -1)
+ : withoutQuery;
+}
+
+export function absoluteUrl(path: string): string {
+ if (!path || path === '/') {
+ return `${SITE_ORIGIN}/`;
+ }
+ return `${SITE_ORIGIN}${path.startsWith('/') ? path : `/${path}`}`;
+}
+
+export function isIndexablePath(rawPath: string): boolean {
+ return INDEXABLE_PATHS.includes(canonicalizePath(rawPath));
+}
+
+export function getPageSeo(rawPath: string): PageSeo {
+ return PAGE_SEO[canonicalizePath(rawPath)] ?? NOT_FOUND_SEO;
+}
diff --git a/src/app/services/seo.service.spec.ts b/src/app/services/seo.service.spec.ts
new file mode 100644
index 0000000..4d0c8c8
--- /dev/null
+++ b/src/app/services/seo.service.spec.ts
@@ -0,0 +1,187 @@
+import { TestBed } from '@angular/core/testing';
+import { Meta, Title } from '@angular/platform-browser';
+import { SeoService, stripHtml } from './seo.service';
+import {
+ DEFAULT_OG_IMAGE,
+ NOT_FOUND_SEO,
+ SITE_ORIGIN,
+ canonicalizePath,
+ getPageSeo,
+ isIndexablePath,
+} from '../data/seo.data';
+
+describe('SeoService', () => {
+ let service: SeoService;
+ let title: Title;
+ let meta: Meta;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [SeoService, Title, Meta],
+ });
+ service = TestBed.inject(SeoService);
+ title = TestBed.inject(Title);
+ meta = TestBed.inject(Meta);
+ });
+
+ it('should create the service', () => {
+ expect(service).toBeTruthy();
+ });
+
+ it('should set unique title, description, canonical, and robots for a known page', () => {
+ service.updateForUrl('/about-us?utm=test');
+
+ expect(title.getTitle()).toBe('About Us | Ditectrev');
+ expect(meta.getTag('name="description"')?.content).toContain(
+ 'digital transformation'
+ );
+ expect(meta.getTag('name="robots"')?.content).toBe(
+ 'index,follow,max-snippet:-1,max-image-preview:large,max-video-preview:-1'
+ );
+ expect(meta.getTag('property="og:url"')?.content).toBe(
+ `${SITE_ORIGIN}/about-us`
+ );
+ expect(meta.getTag('property="og:image"')?.content).toBe(DEFAULT_OG_IMAGE);
+ expect(meta.getTag('name="twitter:card"')?.content).toBe('summary');
+
+ const canonical = document.querySelector('link[rel="canonical"]');
+ expect(canonical?.getAttribute('href')).toBe(`${SITE_ORIGIN}/about-us`);
+ });
+
+ it('should noindex unknown routes and keep a self-referencing canonical', () => {
+ service.updateForUrl('/this-page-does-not-exist');
+
+ expect(title.getTitle()).toBe(NOT_FOUND_SEO.title);
+ expect(meta.getTag('name="robots"')?.content).toBe('noindex, follow');
+ expect(
+ document.querySelector('link[rel="canonical"]')?.getAttribute('href')
+ ).toBe(`${SITE_ORIGIN}/this-page-does-not-exist`);
+ });
+
+ it('should inject Organization, WebSite, and BreadcrumbList JSON-LD', () => {
+ service.updateForUrl('/');
+
+ const script = document.getElementById('ditectrev-jsonld');
+ expect(script).toBeTruthy();
+ const parsed = JSON.parse(script?.textContent ?? '{}');
+ const types = parsed['@graph'].map((node: { '@type': unknown }) => node['@type']);
+ expect(types).toContain('WebSite');
+ expect(JSON.stringify(types)).toContain('Organization');
+ expect(types).toContain('BreadcrumbList');
+ const org = parsed['@graph'].find(
+ (node: { '@id'?: string }) => node['@id'] === `${SITE_ORIGIN}/#organization`
+ );
+ expect(org?.knowsAbout).toContain('Cyber Security');
+ expect(org?.hasOfferCatalog?.itemListElement?.length).toBe(3);
+ const homePage = parsed['@graph'].find(
+ (node: { '@type': string; mainEntity?: unknown }) =>
+ node['@type'] === 'WebPage' && Array.isArray(node.mainEntity)
+ );
+ expect(homePage.mainEntity.length).toBeGreaterThan(0);
+ expect(org?.taxID).toBe('PL9121899240');
+ expect(
+ document.querySelector('link[rel="describedby"]')?.getAttribute('href')
+ ).toBe(`${SITE_ORIGIN}/llms.txt`);
+ expect(
+ document
+ .querySelector('link[rel="alternate"][type="text/markdown"]')
+ ?.getAttribute('href')
+ ).toBe(`${SITE_ORIGIN}/llms.txt`);
+ });
+
+ it('should inject FAQPage JSON-LD with question and answer pairs from existing FAQ copy', () => {
+ service.updateForUrl('/faq');
+
+ const script = document.getElementById('ditectrev-jsonld');
+ const parsed = JSON.parse(script?.textContent ?? '{}');
+ const faq = parsed['@graph'].find(
+ (node: { '@type': string }) => node['@type'] === 'FAQPage'
+ );
+ expect(faq).toBeDefined();
+ expect(faq.mainEntity.length).toBeGreaterThan(5);
+ expect(faq.mainEntity[0]['@type']).toBe('Question');
+ expect(faq.mainEntity[0].acceptedAnswer['@type']).toBe('Answer');
+ expect(faq.mainEntity[0].acceptedAnswer.text).not.toContain(' {
+ service.updateForUrl('/services/cyber-security');
+
+ const script = document.getElementById('ditectrev-jsonld');
+ const parsed = JSON.parse(script?.textContent ?? '{}');
+ const serviceNode = parsed['@graph'].find(
+ (node: { '@type': string }) => node['@type'] === 'Service'
+ );
+ expect(serviceNode?.name).toBe('Cyber Security');
+ expect(serviceNode?.url).toBe(`${SITE_ORIGIN}/services/cyber-security`);
+ expect(serviceNode?.hasOfferCatalog?.itemListElement?.length).toBe(12);
+ });
+
+ it('should inject HowTo JSON-LD for the methodology page', () => {
+ service.updateForUrl('/methodology');
+
+ const script = document.getElementById('ditectrev-jsonld');
+ const parsed = JSON.parse(script?.textContent ?? '{}');
+ const howTo = parsed['@graph'].find(
+ (node: { '@type': string }) => node['@type'] === 'HowTo'
+ );
+ expect(howTo?.name).toBe('Ditectrev project methodology');
+ expect(howTo?.step?.length).toBe(5);
+ expect(howTo?.step[0].name).toBe('Business Analysis');
+ });
+
+ it('should inject DefinedTermSet JSON-LD for the glossary page', () => {
+ service.updateForUrl('/glossary');
+
+ const script = document.getElementById('ditectrev-jsonld');
+ const parsed = JSON.parse(script?.textContent ?? '{}');
+ const termSet = parsed['@graph'].find(
+ (node: { '@type': string }) => node['@type'] === 'DefinedTermSet'
+ );
+ expect(termSet?.hasDefinedTerm?.length).toBeGreaterThan(20);
+ expect(termSet.hasDefinedTerm[0]['@type']).toBe('DefinedTerm');
+ expect(termSet.hasDefinedTerm[0].name).toBeTruthy();
+ });
+
+ it('should inject ItemList JSON-LD for the services overview', () => {
+ service.updateForUrl('/services');
+
+ const script = document.getElementById('ditectrev-jsonld');
+ const parsed = JSON.parse(script?.textContent ?? '{}');
+ const list = parsed['@graph'].find(
+ (node: { '@type': string }) => node['@type'] === 'ItemList'
+ );
+ expect(list?.itemListElement?.length).toBe(3);
+ expect(list.itemListElement[0].name).toBe('Cyber Security');
+ });
+});
+
+describe('stripHtml', () => {
+ it('should keep link text and remove tags from FAQ-style markup', () => {
+ expect(
+ stripHtml(
+ 'Please check our Privacy & Security policy.'
+ )
+ ).toBe('Please check our Privacy & Security policy.');
+ });
+
+ it('should not leave nested or unclosed script tags', () => {
+ expect(stripHtml('<')).not.toMatch(/')).not.toMatch(/