From c903fed0f5caba9644828a1c0e70e0abe1a86c91 Mon Sep 17 00:00:00 2001 From: Elliot Hesp Date: Thu, 16 May 2019 16:59:01 +0100 Subject: [PATCH] feat(gatsby-plugin-canonical-urls): add option to strip query string (#13339) --- .../gatsby-plugin-canonical-urls/README.md | 18 ++++++ .../__snapshots__/gatsby-ssr.js.snap | 50 +++++++++++++++- .../src/__tests__/gatsby-browser.js | 58 +++++++++++++++++++ .../src/__tests__/gatsby-ssr.js | 41 ++++++++++++- .../src/gatsby-browser.js | 28 +++++---- .../src/gatsby-ssr.js | 25 ++++++-- 6 files changed, 201 insertions(+), 19 deletions(-) create mode 100644 packages/gatsby-plugin-canonical-urls/src/__tests__/gatsby-browser.js diff --git a/packages/gatsby-plugin-canonical-urls/README.md b/packages/gatsby-plugin-canonical-urls/README.md index 1e899ae54d069..d7faf05820297 100644 --- a/packages/gatsby-plugin-canonical-urls/README.md +++ b/packages/gatsby-plugin-canonical-urls/README.md @@ -30,3 +30,21 @@ a `rel=canonical` e.g. ```html ``` + +### Excluding search parameters + +URL search parameters are included in the canonical URL by default. If you worry about duplicate content because for example `/blog` and `/blog?tag=foobar` will be indexed separately, you should set the option `stripQueryString` to `true`. The latter will then be changed to `/blog`. + +```title=gatsby-config.js +module.exports = { + plugins: [ + { + resolve: `gatsby-plugin-canonical-urls`, + options: { + siteUrl: `https://www.example.com`, + stripQueryString: true, + }, + }, + ] +} +``` diff --git a/packages/gatsby-plugin-canonical-urls/src/__tests__/__snapshots__/gatsby-ssr.js.snap b/packages/gatsby-plugin-canonical-urls/src/__tests__/__snapshots__/gatsby-ssr.js.snap index 9970d188a637f..d996b70a7fb84 100644 --- a/packages/gatsby-plugin-canonical-urls/src/__tests__/__snapshots__/gatsby-ssr.js.snap +++ b/packages/gatsby-plugin-canonical-urls/src/__tests__/__snapshots__/gatsby-ssr.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Adds canonical link to head correctly creates a canonical link if siteUrl is set 1`] = ` +exports[`gatsby-plugin-canonical-urls creates a canonical link if siteUrl is set 1`] = ` [MockFunction] { "calls": Array [ Array [ @@ -23,4 +23,50 @@ exports[`Adds canonical link to head correctly creates a canonical link if siteU } `; -exports[`Adds canonical link to head correctly does not create a canonical link if siteUrl is not set 1`] = `[MockFunction]`; +exports[`gatsby-plugin-canonical-urls does not create a canonical link if siteUrl is not set 1`] = `[MockFunction]`; + +exports[`gatsby-plugin-canonical-urls strips a trailing slash on the siteUrl and leaves search parameters 1`] = ` +[MockFunction] { + "calls": Array [ + Array [ + Array [ + , + ], + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + ], +} +`; + +exports[`gatsby-plugin-canonical-urls strips search paramaters if option stripQueryString is true 1`] = ` +[MockFunction] { + "calls": Array [ + Array [ + Array [ + , + ], + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, + }, + ], +} +`; diff --git a/packages/gatsby-plugin-canonical-urls/src/__tests__/gatsby-browser.js b/packages/gatsby-plugin-canonical-urls/src/__tests__/gatsby-browser.js new file mode 100644 index 0000000000000..e74fad5cfdb60 --- /dev/null +++ b/packages/gatsby-plugin-canonical-urls/src/__tests__/gatsby-browser.js @@ -0,0 +1,58 @@ +const { onRouteUpdate } = require(`../gatsby-browser`) + +describe(`gatsby-plugin-canonical-urls`, () => { + beforeEach(() => { + global.document.head.innerHTML = `` + }) + + it(`should update the href`, () => { + onRouteUpdate({ location: { pathname: `/hogwarts`, hash: ``, search: `` } }) + + expect(global.document.head.innerHTML).toMatchInlineSnapshot( + `""` + ) + }) + it(`should keep the hash`, () => { + onRouteUpdate({ + location: { pathname: `/hogwarts`, hash: `#harry-potter`, search: `` }, + }) + + expect(global.document.head.innerHTML).toMatchInlineSnapshot( + `""` + ) + }) + it(`shouldn't strip search parameter by default`, () => { + onRouteUpdate({ + location: { + pathname: `/hogwarts`, + hash: ``, + search: `?house=gryffindor`, + }, + }) + + expect(global.document.head.innerHTML).toMatchInlineSnapshot( + `""` + ) + }) + it(`should strip search paramaters if option stripQueryString is true`, () => { + const pluginOptions = { + stripQueryString: true, + } + + onRouteUpdate( + { + location: { + pathname: `/hogwarts`, + hash: ``, + search: `?house=gryffindor`, + }, + }, + pluginOptions + ) + + expect(global.document.head.innerHTML).toMatchInlineSnapshot( + `""` + ) + }) +}) diff --git a/packages/gatsby-plugin-canonical-urls/src/__tests__/gatsby-ssr.js b/packages/gatsby-plugin-canonical-urls/src/__tests__/gatsby-ssr.js index fe92f2828111a..afeb7f99e2b07 100644 --- a/packages/gatsby-plugin-canonical-urls/src/__tests__/gatsby-ssr.js +++ b/packages/gatsby-plugin-canonical-urls/src/__tests__/gatsby-ssr.js @@ -1,6 +1,6 @@ const { onRenderBody } = require(`../gatsby-ssr`) -describe(`Adds canonical link to head correctly`, () => { +describe(`gatsby-plugin-canonical-urls`, () => { it(`creates a canonical link if siteUrl is set`, async () => { const pluginOptions = { siteUrl: `http://someurl.com`, @@ -20,6 +20,45 @@ describe(`Adds canonical link to head correctly`, () => { expect(setHeadComponents).toHaveBeenCalledTimes(1) }) + it(`strips a trailing slash on the siteUrl and leaves search parameters`, async () => { + const pluginOptions = { + siteUrl: `http://someurl.com/`, + } + const setHeadComponents = jest.fn() + const pathname = `/somepost?tag=foobar` + + await onRenderBody( + { + setHeadComponents, + pathname, + }, + pluginOptions + ) + + expect(setHeadComponents).toMatchSnapshot() + expect(setHeadComponents).toHaveBeenCalledTimes(1) + }) + + it(`strips search paramaters if option stripQueryString is true`, async () => { + const pluginOptions = { + siteUrl: `http://someurl.com`, + stripQueryString: true, + } + const setHeadComponents = jest.fn() + const pathname = `/somepost?tag=foobar` + + await onRenderBody( + { + setHeadComponents, + pathname, + }, + pluginOptions + ) + + expect(setHeadComponents).toMatchSnapshot() + expect(setHeadComponents).toHaveBeenCalledTimes(1) + }) + it(`does not create a canonical link if siteUrl is not set`, async () => { const pluginOptions = {} const setHeadComponents = jest.fn() diff --git a/packages/gatsby-plugin-canonical-urls/src/gatsby-browser.js b/packages/gatsby-plugin-canonical-urls/src/gatsby-browser.js index 687b4fcf54629..4fd35ae5aac0e 100644 --- a/packages/gatsby-plugin-canonical-urls/src/gatsby-browser.js +++ b/packages/gatsby-plugin-canonical-urls/src/gatsby-browser.js @@ -1,14 +1,22 @@ -exports.onRouteUpdate = ({ location }) => { +exports.onRouteUpdate = ( + { location }, + pluginOptions = { stripQueryString: false } +) => { const domElem = document.querySelector(`link[rel='canonical']`) - var existingValue = domElem.getAttribute(`href`) - var baseProtocol = domElem.getAttribute(`data-baseProtocol`) - var baseHost = domElem.getAttribute(`data-baseHost`) + const existingValue = domElem.getAttribute(`href`) + const baseProtocol = domElem.getAttribute(`data-baseProtocol`) + const baseHost = domElem.getAttribute(`data-baseHost`) if (existingValue && baseProtocol && baseHost) { - domElem.setAttribute( - `href`, - `${baseProtocol}//${baseHost}${location.pathname}${location.search}${ - location.hash - }` - ) + let value = `${baseProtocol}//${baseHost}${location.pathname}` + + const { stripQueryString } = pluginOptions + + if (!stripQueryString) { + value += location.search + } + + value += location.hash + + domElem.setAttribute(`href`, `${value}`) } } diff --git a/packages/gatsby-plugin-canonical-urls/src/gatsby-ssr.js b/packages/gatsby-plugin-canonical-urls/src/gatsby-ssr.js index 87e7dd9cf75eb..74c623f055b40 100644 --- a/packages/gatsby-plugin-canonical-urls/src/gatsby-ssr.js +++ b/packages/gatsby-plugin-canonical-urls/src/gatsby-ssr.js @@ -6,15 +6,28 @@ exports.onRenderBody = ( pluginOptions ) => { if (pluginOptions && pluginOptions.siteUrl) { - const parsedUrl = url.parse(pluginOptions.siteUrl) - const myUrl = `${pluginOptions.siteUrl}${pathname}` + const siteUrl = pluginOptions.siteUrl.replace(/\/$/, ``) + const parsed = url.parse(`${siteUrl}${pathname}`) + const stripQueryString = + typeof pluginOptions.stripQueryString !== `undefined` + ? pluginOptions.stripQueryString + : false + + let pageUrl = `` + + if (stripQueryString) { + pageUrl = `${parsed.protocol}//${parsed.host}${parsed.pathname}` + } else { + pageUrl = parsed.href + } + setHeadComponents([ , ]) }