From 35f9c74d51c67417c00259c1ae9cc1742c4b60cf Mon Sep 17 00:00:00 2001 From: Vasyl Yaremchuk Date: Thu, 14 Aug 2025 10:03:04 +0300 Subject: [PATCH 1/7] Simplified version that doesn't break the deploy. --- website/modules/@apostrophecms/seo/index.js | 25 +++---------------- .../@apostrophecms/seo/lib/gtm-utils.js | 25 +++++++++++++++++++ .../@apostrophecms/seo/views/gtmBody.html | 4 +++ .../@apostrophecms/seo/views/gtmHead.html | 7 ++++++ .../seo/views/tagManagerBody.html | 5 +--- .../seo/views/tagManagerHead.html | 8 +----- website/package.json | 1 - website/views/layout.html | 2 ++ 8 files changed, 44 insertions(+), 33 deletions(-) create mode 100644 website/modules/@apostrophecms/seo/lib/gtm-utils.js create mode 100644 website/modules/@apostrophecms/seo/views/gtmBody.html create mode 100644 website/modules/@apostrophecms/seo/views/gtmHead.html diff --git a/website/modules/@apostrophecms/seo/index.js b/website/modules/@apostrophecms/seo/index.js index b4e8fada..ef0ebf78 100644 --- a/website/modules/@apostrophecms/seo/index.js +++ b/website/modules/@apostrophecms/seo/index.js @@ -1,3 +1,5 @@ +const gtmUtils = require('./lib/gtm-utils'); + module.exports = { improve: '@apostrophecms/seo', init(self) { @@ -7,25 +9,6 @@ module.exports = { self.apos.template.prepend('head', '@apostrophecms/seo:metaHead'); }, components(self) { - /* - * Resolve GTM ID from global override or module options and validate it - */ - const sanitizeGtmId = (id) => { - const value = String(id || '').trim(); - if (/^gtm-[\da-z]+$/iu.test(value)) { - return value.toUpperCase(); - } - return ''; - }; - const resolveGtmId = (req) => { - const fromGlobal = req?.data?.global?.seoGoogleTagManager; - const fromOptions = self.options?.googleTagManager?.id; - const candidate = - (fromGlobal && String(fromGlobal).trim()) || - (fromOptions && String(fromOptions).trim()); - return sanitizeGtmId(candidate); - }; - return { metaHead(req, data) { // Only on front-end page requests @@ -38,7 +21,7 @@ module.exports = { if (!req?.data?.page) { return {}; } - const gtmId = resolveGtmId(req); + const gtmId = gtmUtils.resolveGtmId(req, self.options); if (gtmId) { return { gtmId }; } @@ -48,7 +31,7 @@ module.exports = { if (!req?.data?.page) { return {}; } - const gtmId = resolveGtmId(req); + const gtmId = gtmUtils.resolveGtmId(req, self.options); if (gtmId) { return { gtmId }; } diff --git a/website/modules/@apostrophecms/seo/lib/gtm-utils.js b/website/modules/@apostrophecms/seo/lib/gtm-utils.js new file mode 100644 index 00000000..07491468 --- /dev/null +++ b/website/modules/@apostrophecms/seo/lib/gtm-utils.js @@ -0,0 +1,25 @@ +module.exports = { + /** + * Sanitize and validate GTM ID + * Only allow valid GTM container IDs (e.g. "GTM-XXXX") + */ + sanitizeGtmId(id) { + const value = String(id || '').trim(); + if (/^gtm-[\da-z]+$/iu.test(value)) { + return value.toUpperCase(); + } + return ''; + }, + + /** + * Resolve GTM ID from global override or module options + */ + resolveGtmId(req, options) { + const fromGlobal = req?.data?.global?.seoGoogleTagManager; + const fromOptions = options?.googleTagManager?.id; + const candidate = + (fromGlobal && String(fromGlobal).trim()) || + (fromOptions && String(fromOptions).trim()); + return this.sanitizeGtmId(candidate); + }, +}; diff --git a/website/modules/@apostrophecms/seo/views/gtmBody.html b/website/modules/@apostrophecms/seo/views/gtmBody.html new file mode 100644 index 00000000..1b5136e6 --- /dev/null +++ b/website/modules/@apostrophecms/seo/views/gtmBody.html @@ -0,0 +1,4 @@ + + + diff --git a/website/modules/@apostrophecms/seo/views/gtmHead.html b/website/modules/@apostrophecms/seo/views/gtmHead.html new file mode 100644 index 00000000..7ea0949f --- /dev/null +++ b/website/modules/@apostrophecms/seo/views/gtmHead.html @@ -0,0 +1,7 @@ + + + diff --git a/website/modules/@apostrophecms/seo/views/tagManagerBody.html b/website/modules/@apostrophecms/seo/views/tagManagerBody.html index faf6d8ae..fb8f0cc6 100644 --- a/website/modules/@apostrophecms/seo/views/tagManagerBody.html +++ b/website/modules/@apostrophecms/seo/views/tagManagerBody.html @@ -1,6 +1,3 @@ {% if data.gtmId %} - - - +{% render '@apostrophecms/seo:gtmBody', data %} {% endif %} diff --git a/website/modules/@apostrophecms/seo/views/tagManagerHead.html b/website/modules/@apostrophecms/seo/views/tagManagerHead.html index 8bc3af43..e4f7b79f 100644 --- a/website/modules/@apostrophecms/seo/views/tagManagerHead.html +++ b/website/modules/@apostrophecms/seo/views/tagManagerHead.html @@ -1,9 +1,3 @@ {% if data.gtmId %} - - - +{% render '@apostrophecms/seo:gtmHead', data %} {% endif %} diff --git a/website/package.json b/website/package.json index f9733e58..1b56d7e8 100644 --- a/website/package.json +++ b/website/package.json @@ -40,7 +40,6 @@ "dependencies": { "@apostrophecms/form": "^1.4.2", "@apostrophecms/import-export": "^3.2.0", - "@apostrophecms/seo": "^1.3.1", "@barba/core": "^2.10.3", "abort-controller": "^3.0.0", "apostrophe": "^4.17.0", diff --git a/website/views/layout.html b/website/views/layout.html index 7bc6d4a1..e75dd0c8 100644 --- a/website/views/layout.html +++ b/website/views/layout.html @@ -14,9 +14,11 @@ {# ✅ Add favicon in the section #} {% block extraHead %} + {{ customSeoGtmHead(data) }} {% endblock %} {% block beforeMain %} + {{ customSeoGtmBody(data) }} Skip to content
Date: Thu, 14 Aug 2025 10:03:04 +0300 Subject: [PATCH 2/7] Fix SonarQube issues. --- website/modules/@apostrophecms/seo/views/gtmBody.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/modules/@apostrophecms/seo/views/gtmBody.html b/website/modules/@apostrophecms/seo/views/gtmBody.html index 1b5136e6..5e350708 100644 --- a/website/modules/@apostrophecms/seo/views/gtmBody.html +++ b/website/modules/@apostrophecms/seo/views/gtmBody.html @@ -1,4 +1,4 @@ +height="0" width="0" style="display:none;visibility:hidden" title="Google Tag Manager"> From 755cd17a4c1d3cada80cd2ccf738ae027d12d86f Mon Sep 17 00:00:00 2001 From: Vasyl Yaremchuk Date: Thu, 14 Aug 2025 10:28:41 +0300 Subject: [PATCH 3/7] Implement CodeRabbit suggestions. --- website/modules/@apostrophecms/seo/index.js | 1 - .../@apostrophecms/seo/lib/gtm-utils.js | 42 +++++++++---------- website/views/layout.html | 4 +- 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/website/modules/@apostrophecms/seo/index.js b/website/modules/@apostrophecms/seo/index.js index ef0ebf78..f5df0e13 100644 --- a/website/modules/@apostrophecms/seo/index.js +++ b/website/modules/@apostrophecms/seo/index.js @@ -1,7 +1,6 @@ const gtmUtils = require('./lib/gtm-utils'); module.exports = { - improve: '@apostrophecms/seo', init(self) { // Ensure SEO components are injected into the template self.apos.template.prepend('body', '@apostrophecms/seo:tagManagerBody'); diff --git a/website/modules/@apostrophecms/seo/lib/gtm-utils.js b/website/modules/@apostrophecms/seo/lib/gtm-utils.js index 07491468..4768430b 100644 --- a/website/modules/@apostrophecms/seo/lib/gtm-utils.js +++ b/website/modules/@apostrophecms/seo/lib/gtm-utils.js @@ -1,25 +1,21 @@ -module.exports = { - /** - * Sanitize and validate GTM ID - * Only allow valid GTM container IDs (e.g. "GTM-XXXX") - */ - sanitizeGtmId(id) { - const value = String(id || '').trim(); - if (/^gtm-[\da-z]+$/iu.test(value)) { - return value.toUpperCase(); - } - return ''; - }, +const sanitizeGtmId = function (id) { + const value = String(id || '') + .trim() + .toUpperCase(); + // Strict GTM container format, e.g., "GTM-XXXXXXX" + if (/^GTM-[\dA-Z]+$/u.test(value)) { + return value; + } + return ''; +}; - /** - * Resolve GTM ID from global override or module options - */ - resolveGtmId(req, options) { - const fromGlobal = req?.data?.global?.seoGoogleTagManager; - const fromOptions = options?.googleTagManager?.id; - const candidate = - (fromGlobal && String(fromGlobal).trim()) || - (fromOptions && String(fromOptions).trim()); - return this.sanitizeGtmId(candidate); - }, +const resolveGtmId = function (req, options) { + const fromGlobal = req?.data?.global?.seoGoogleTagManager; + const fromOptions = options?.googleTagManager?.id; + const candidate = + (fromGlobal && String(fromGlobal).trim()) || + (fromOptions && String(fromOptions).trim()); + return sanitizeGtmId(candidate); }; + +module.exports = { sanitizeGtmId, resolveGtmId }; diff --git a/website/views/layout.html b/website/views/layout.html index e75dd0c8..8e1b0b5a 100644 --- a/website/views/layout.html +++ b/website/views/layout.html @@ -14,11 +14,11 @@ {# ✅ Add favicon in the section #} {% block extraHead %} - {{ customSeoGtmHead(data) }} + {% render '@apostrophecms/seo:tagManagerHead', data %} {% endblock %} {% block beforeMain %} - {{ customSeoGtmBody(data) }} + {% render '@apostrophecms/seo:tagManagerBody', data %} Skip to content
Date: Thu, 14 Aug 2025 10:42:13 +0300 Subject: [PATCH 4/7] Fix dependencies from @apostrophecms/seo that we don't need. --- website/app.js | 9 +-------- website/modules/@apostrophecms/seo/index.js | 5 +++++ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/website/app.js b/website/app.js index 887e5f87..e9138ea1 100644 --- a/website/app.js +++ b/website/app.js @@ -34,14 +34,7 @@ function createAposConfig() { }, }, }, - // GTM setup and dependencies - '@apostrophecms/seo': { - options: { - googleTagManager: { - id: process.env.GOOGLE_TAG_MANAGER_ID, - }, - }, - }, + // GTM setup handled by local SEO module '@apostrophecms/global': {}, // Make getEnv function available to templates '@apostrophecms/template': { diff --git a/website/modules/@apostrophecms/seo/index.js b/website/modules/@apostrophecms/seo/index.js index f5df0e13..f4a62f40 100644 --- a/website/modules/@apostrophecms/seo/index.js +++ b/website/modules/@apostrophecms/seo/index.js @@ -1,6 +1,11 @@ const gtmUtils = require('./lib/gtm-utils'); module.exports = { + options: { + googleTagManager: { + id: process.env.GOOGLE_TAG_MANAGER_ID, + }, + }, init(self) { // Ensure SEO components are injected into the template self.apos.template.prepend('body', '@apostrophecms/seo:tagManagerBody'); From 06d8735cb733a56a8a68ea162965c9c0b5d49079 Mon Sep 17 00:00:00 2001 From: Vasyl Yaremchuk Date: Thu, 14 Aug 2025 10:56:17 +0300 Subject: [PATCH 5/7] Implement CodeRabbit suggestions: 2nd round. --- website/app.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/app.js b/website/app.js index e9138ea1..63cc48a0 100644 --- a/website/app.js +++ b/website/app.js @@ -34,7 +34,8 @@ function createAposConfig() { }, }, }, - // GTM setup handled by local SEO module + // Enable local SEO module with GTM integration + '@apostrophecms/seo': {}, '@apostrophecms/global': {}, // Make getEnv function available to templates '@apostrophecms/template': { From c0b222d132161f558dba1975ac132fc7b99c1d42 Mon Sep 17 00:00:00 2001 From: Vasyl Yaremchuk Date: Thu, 14 Aug 2025 11:14:52 +0300 Subject: [PATCH 6/7] Fix local errors. --- website/modules/@apostrophecms/seo/index.js | 8 ++++---- website/modules/@apostrophecms/seo/views/gtmBody.html | 4 ---- website/modules/@apostrophecms/seo/views/gtmHead.html | 7 ------- .../modules/@apostrophecms/seo/views/tagManagerBody.html | 5 ++++- .../modules/@apostrophecms/seo/views/tagManagerHead.html | 8 +++++++- website/views/layout.html | 4 ++-- 6 files changed, 17 insertions(+), 19 deletions(-) delete mode 100644 website/modules/@apostrophecms/seo/views/gtmBody.html delete mode 100644 website/modules/@apostrophecms/seo/views/gtmHead.html diff --git a/website/modules/@apostrophecms/seo/index.js b/website/modules/@apostrophecms/seo/index.js index f4a62f40..927e185b 100644 --- a/website/modules/@apostrophecms/seo/index.js +++ b/website/modules/@apostrophecms/seo/index.js @@ -7,10 +7,10 @@ module.exports = { }, }, init(self) { - // Ensure SEO components are injected into the template - self.apos.template.prepend('body', '@apostrophecms/seo:tagManagerBody'); - self.apos.template.append('head', '@apostrophecms/seo:tagManagerHead'); - self.apos.template.prepend('head', '@apostrophecms/seo:metaHead'); + /* + * SEO components are called directly from layout.html template + * No automatic injection needed to avoid duplication + */ }, components(self) { return { diff --git a/website/modules/@apostrophecms/seo/views/gtmBody.html b/website/modules/@apostrophecms/seo/views/gtmBody.html deleted file mode 100644 index 5e350708..00000000 --- a/website/modules/@apostrophecms/seo/views/gtmBody.html +++ /dev/null @@ -1,4 +0,0 @@ - - - diff --git a/website/modules/@apostrophecms/seo/views/gtmHead.html b/website/modules/@apostrophecms/seo/views/gtmHead.html deleted file mode 100644 index 7ea0949f..00000000 --- a/website/modules/@apostrophecms/seo/views/gtmHead.html +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/website/modules/@apostrophecms/seo/views/tagManagerBody.html b/website/modules/@apostrophecms/seo/views/tagManagerBody.html index fb8f0cc6..faf6d8ae 100644 --- a/website/modules/@apostrophecms/seo/views/tagManagerBody.html +++ b/website/modules/@apostrophecms/seo/views/tagManagerBody.html @@ -1,3 +1,6 @@ {% if data.gtmId %} -{% render '@apostrophecms/seo:gtmBody', data %} + + + {% endif %} diff --git a/website/modules/@apostrophecms/seo/views/tagManagerHead.html b/website/modules/@apostrophecms/seo/views/tagManagerHead.html index e4f7b79f..8bc3af43 100644 --- a/website/modules/@apostrophecms/seo/views/tagManagerHead.html +++ b/website/modules/@apostrophecms/seo/views/tagManagerHead.html @@ -1,3 +1,9 @@ {% if data.gtmId %} -{% render '@apostrophecms/seo:gtmHead', data %} + + + {% endif %} diff --git a/website/views/layout.html b/website/views/layout.html index 8e1b0b5a..bfa12ac2 100644 --- a/website/views/layout.html +++ b/website/views/layout.html @@ -14,11 +14,11 @@ {# ✅ Add favicon in the section #} {% block extraHead %} - {% render '@apostrophecms/seo:tagManagerHead', data %} + {% component '@apostrophecms/seo:tagManagerHead' with data %} {% endblock %} {% block beforeMain %} - {% render '@apostrophecms/seo:tagManagerBody', data %} + {% component '@apostrophecms/seo:tagManagerBody' with data %} Skip to content
Date: Thu, 14 Aug 2025 11:28:44 +0300 Subject: [PATCH 7/7] Implement CodeRabbit suggestions: 3nd round. --- website/modules/@apostrophecms/seo/index.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/website/modules/@apostrophecms/seo/index.js b/website/modules/@apostrophecms/seo/index.js index 927e185b..e0bd649e 100644 --- a/website/modules/@apostrophecms/seo/index.js +++ b/website/modules/@apostrophecms/seo/index.js @@ -6,26 +6,15 @@ module.exports = { id: process.env.GOOGLE_TAG_MANAGER_ID, }, }, - init(self) { - /* - * SEO components are called directly from layout.html template - * No automatic injection needed to avoid duplication - */ - }, + // No init hook required (layout renders components explicitly). components(self) { + const getGtmId = (req) => gtmUtils.resolveGtmId(req, self.options); return { - metaHead(req, data) { - // Only on front-end page requests - if (!req?.data?.page) { - return {}; - } - return {}; - }, tagManagerBody(req, data) { if (!req?.data?.page) { return {}; } - const gtmId = gtmUtils.resolveGtmId(req, self.options); + const gtmId = getGtmId(req); if (gtmId) { return { gtmId }; } @@ -35,7 +24,7 @@ module.exports = { if (!req?.data?.page) { return {}; } - const gtmId = gtmUtils.resolveGtmId(req, self.options); + const gtmId = getGtmId(req); if (gtmId) { return { gtmId }; }