diff --git a/website/app.js b/website/app.js index 0ff482f1..e8192e1d 100644 --- a/website/app.js +++ b/website/app.js @@ -67,6 +67,7 @@ function createAposConfig() { }, // Enable local SEO module with GTM integration '@apostrophecms/seo': {}, + '@apostrophecms/sitemap': {}, '@apostrophecms/global': {}, // Make getEnv function available to templates '@apostrophecms/template': { @@ -79,6 +80,7 @@ function createAposConfig() { // Add global data module 'global-data': {}, + 'robots': {}, // Shared constants module '@apostrophecms/shared-constants': {}, diff --git a/website/modules/@apostrophecms/sitemap/index.js b/website/modules/@apostrophecms/sitemap/index.js new file mode 100644 index 00000000..b3eaa530 --- /dev/null +++ b/website/modules/@apostrophecms/sitemap/index.js @@ -0,0 +1,5 @@ +module.exports = { + options: { + cacheLifetime: 60 * 60, + }, +}; diff --git a/website/modules/case-studies-page/index.js b/website/modules/case-studies-page/index.js index 1758a6ae..350d344d 100644 --- a/website/modules/case-studies-page/index.js +++ b/website/modules/case-studies-page/index.js @@ -165,6 +165,36 @@ const runSetupIndexData = async function (self, req) { } }; +const buildIndexSeoData = function (req) { + const query = req.query || {}; + const hasFilterParams = + Boolean(query.search) || + Boolean(query.industry) || + Boolean(query.stack) || + Boolean(query.caseStudyType) || + Boolean(query.partner); + const pageNumber = Number(query.page || 1); + const hasPaginationParam = Number.isFinite(pageNumber) && pageNumber > 1; + const shouldNoindex = hasFilterParams || hasPaginationParam; + let pageUrl = '/cases'; + if (req.data && req.data.page && req.data.page.slug) { + pageUrl = req.data.page.slug; + } + let robots = 'index,follow'; + if (shouldNoindex) { + robots = 'noindex,follow'; + } + return { + canonicalUrl: pageUrl, + robots, + }; +}; + +const runSetupIndexSeoData = function (req) { + req.data ||= {}; + req.data.caseListingSeo = buildIndexSeoData(req); +}; + const runSetupShowData = async function (self, req) { try { const navigation = await NavigationService.getNavigationDataForPage( @@ -219,6 +249,7 @@ module.exports = { await self.resolveSearchRelationships(req); await self.applyEnhancedSearchResults(req); await self.setupIndexData(req); + self.setupIndexSeoData(req); }; const superBeforeShow = self.beforeShow; @@ -244,6 +275,9 @@ module.exports = { setupIndexData(req) { return runSetupIndexData(self, req); }, + setupIndexSeoData(req) { + return runSetupIndexSeoData(req); + }, setupShowData(req) { return runSetupShowData(self, req); }, diff --git a/website/modules/case-studies-page/services/UrlService.js b/website/modules/case-studies-page/services/UrlService.js index f18963a9..f4b15c96 100644 --- a/website/modules/case-studies-page/services/UrlService.js +++ b/website/modules/case-studies-page/services/UrlService.js @@ -153,6 +153,7 @@ class UrlService { reqCopy.data.backUrl = UrlService.buildCaseStudyUrl('/cases', queryParams); reqCopy.data.query = queryParams; + reqCopy.data.hasQueryParams = Object.keys(req.query || {}).length > 0; } } diff --git a/website/modules/case-studies-page/views/index.html b/website/modules/case-studies-page/views/index.html index 9680b986..663a9098 100644 --- a/website/modules/case-studies-page/views/index.html +++ b/website/modules/case-studies-page/views/index.html @@ -1,6 +1,11 @@ {# modules/case-studies-page/views/index.html #} {% extends "layout.html" %} {% import '@apostrophecms/pager:macros.html' as pager with context %} +{% block extraHead %} + {{ super() }} + + +{% endblock %} {% block main %}
+ +{% endblock %} +{% block main %}
diff --git a/website/modules/robots/index.js b/website/modules/robots/index.js new file mode 100644 index 00000000..718eecf5 --- /dev/null +++ b/website/modules/robots/index.js @@ -0,0 +1,35 @@ +module.exports = { + routes(self) { + return { + get: { + '/robots.txt': (req, res) => { + const baseUrl = self.apos.baseUrl || ''; + let baseHost = ''; + try { + baseHost = new URL(baseUrl).hostname; + } catch (error) { + self.apos.util.warn('Invalid baseUrl for robots.txt route', error); + baseHost = ''; + } + const isProduction = process.env.NODE_ENV === 'production'; + const productionHosts = [ + 'speedandfunction.com', + 'www.speedandfunction.com', + ]; + const isProductionHost = productionHosts.includes(baseHost); + const parsedBaseUrl = new URL(baseUrl); + const normalizedBaseUrl = parsedBaseUrl.origin; + if (!isProduction || !isProductionHost) { + return res.type('text/plain').send('User-agent: *\nDisallow: /\n'); + } + + const robotsContent = + 'User-agent: *\n' + + 'Allow: /\n\n' + + `Sitemap: ${normalizedBaseUrl}/sitemap.xml\n`; + return res.type('text/plain').send(robotsContent); + }, + }, + }; + }, +}; diff --git a/website/package-lock.json b/website/package-lock.json index 52d6f083..99427fba 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -12,6 +12,7 @@ "@apostrophecms/form": "^1.4.2", "@apostrophecms/import-export": "^3.2.0", "@apostrophecms/security-headers": "^1.0.2", + "@apostrophecms/sitemap": "^1.2.0", "@barba/core": "^2.10.3", "abort-controller": "^3.0.0", "apostrophe": "^4.17.0", @@ -119,6 +120,15 @@ "integrity": "sha512-BoBIRdWkXSxZMt8CFlKUevOm2BU73wRHACsQTjFiNBfUso2MWy2UuufId3akR5WAnCsaRRUSUCgg8CbaOiSxPA==", "license": "MIT" }, + "node_modules/@apostrophecms/sitemap": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@apostrophecms/sitemap/-/sitemap-1.2.0.tgz", + "integrity": "sha512-NmlZ+2+XM9hDKQgk2TQM6BPKSLAQJzGSwPNvc5lN3X3C/DPW/e6KqsuPfnZ0/65O9yKN8CbGYsUCQyEGAT+Caw==", + "license": "MIT", + "dependencies": { + "common-tags": "^1.8.0" + } + }, "node_modules/@apostrophecms/vue-material-design-icons": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@apostrophecms/vue-material-design-icons/-/vue-material-design-icons-1.0.0.tgz", diff --git a/website/package.json b/website/package.json index 81f940d3..0c0ce836 100644 --- a/website/package.json +++ b/website/package.json @@ -41,6 +41,7 @@ "@apostrophecms/form": "^1.4.2", "@apostrophecms/import-export": "^3.2.0", "@apostrophecms/security-headers": "^1.0.2", + "@apostrophecms/sitemap": "^1.2.0", "@barba/core": "^2.10.3", "abort-controller": "^3.0.0", "apostrophe": "^4.17.0",