Skip to content

Commit

Permalink
feat(gatsby-plugin-canonical-urls): add option to strip query string (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Ehesp authored and pieh committed May 16, 2019
1 parent fd0f381 commit c903fed
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 19 deletions.
18 changes: 18 additions & 0 deletions packages/gatsby-plugin-canonical-urls/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,21 @@ a `rel=canonical` e.g.
```html
<link rel="canonical" href="https://www.example.com/about-us/" />
```

### 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,
},
},
]
}
```
Original file line number Diff line number Diff line change
@@ -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 [
Expand All @@ -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 [
<link
data-basehost="someurl.com"
data-baseprotocol="http:"
href="http://someurl.com/somepost?tag=foobar"
rel="canonical"
/>,
],
],
],
"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 [
<link
data-basehost="someurl.com"
data-baseprotocol="http:"
href="http://someurl.com/somepost"
rel="canonical"
/>,
],
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
const { onRouteUpdate } = require(`../gatsby-browser`)

describe(`gatsby-plugin-canonical-urls`, () => {
beforeEach(() => {
global.document.head.innerHTML = `<link data-basehost="someurl.com" data-baseprotocol="http:" href="http://someurl.com/somepost" rel="canonical"
/>`
})

it(`should update the href`, () => {
onRouteUpdate({ location: { pathname: `/hogwarts`, hash: ``, search: `` } })

expect(global.document.head.innerHTML).toMatchInlineSnapshot(
`"<link data-basehost=\\"someurl.com\\" data-baseprotocol=\\"http:\\" href=\\"http://someurl.com/hogwarts\\" rel=\\"canonical\\">"`
)
})
it(`should keep the hash`, () => {
onRouteUpdate({
location: { pathname: `/hogwarts`, hash: `#harry-potter`, search: `` },
})

expect(global.document.head.innerHTML).toMatchInlineSnapshot(
`"<link data-basehost=\\"someurl.com\\" data-baseprotocol=\\"http:\\" href=\\"http://someurl.com/hogwarts#harry-potter\\" rel=\\"canonical\\">"`
)
})
it(`shouldn't strip search parameter by default`, () => {
onRouteUpdate({
location: {
pathname: `/hogwarts`,
hash: ``,
search: `?house=gryffindor`,
},
})

expect(global.document.head.innerHTML).toMatchInlineSnapshot(
`"<link data-basehost=\\"someurl.com\\" data-baseprotocol=\\"http:\\" href=\\"http://someurl.com/hogwarts?house=gryffindor\\" rel=\\"canonical\\">"`
)
})
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(
`"<link data-basehost=\\"someurl.com\\" data-baseprotocol=\\"http:\\" href=\\"http://someurl.com/hogwarts\\" rel=\\"canonical\\">"`
)
})
})
Original file line number Diff line number Diff line change
@@ -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`,
Expand All @@ -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()
Expand Down
28 changes: 18 additions & 10 deletions packages/gatsby-plugin-canonical-urls/src/gatsby-browser.js
Original file line number Diff line number Diff line change
@@ -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}`)
}
}
25 changes: 19 additions & 6 deletions packages/gatsby-plugin-canonical-urls/src/gatsby-ssr.js
Original file line number Diff line number Diff line change
Expand Up @@ -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([
<link
rel="canonical"
key={myUrl}
href={myUrl}
data-baseprotocol={parsedUrl.protocol}
data-basehost={parsedUrl.host}
key={pageUrl}
href={pageUrl}
data-baseprotocol={parsed.protocol}
data-basehost={parsed.host}
/>,
])
}
Expand Down

0 comments on commit c903fed

Please sign in to comment.