Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions middleware/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { setDefaultFastlySurrogateKey } from './set-fastly-surrogate-key.js'
import setFastlyCacheHeaders from './set-fastly-cache-headers.js'
import reqUtils from './req-utils.js'
import recordRedirect from './record-redirect.js'
import connectSlashes from 'connect-slashes'
import handleErrors from './handle-errors.js'
import handleInvalidPaths from './handle-invalid-paths.js'
import handleNextDataPath from './handle-next-data-path.js'
Expand Down Expand Up @@ -67,6 +66,7 @@ import protect from './overload-protection.js'
import fastHead from './fast-head.js'
import fastlyCacheTest from './fastly-cache-test.js'
import fastRootRedirect from './fast-root-redirect.js'
import trailingSlashes from './trailing-slashes.js'

const { DEPLOYMENT_ENV, NODE_ENV } = process.env
const isDevelopment = NODE_ENV === 'development'
Expand Down Expand Up @@ -263,7 +263,7 @@ export default function (app) {

// *** Redirects, 3xx responses ***
// I ordered these by use frequency
app.use(connectSlashes(false))
app.use(instrument(trailingSlashes, './redirects/trailing-slashes'))
app.use(instrument(redirectsExternal, './redirects/external'))
app.use(instrument(languageCodeRedirects, './redirects/language-code-redirects')) // Must come before contextualizers
app.use(instrument(handleRedirects, './redirects/handle-redirects')) // Must come before contextualizers
Expand Down
25 changes: 25 additions & 0 deletions middleware/trailing-slashes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { cacheControlFactory } from './cache-control.js'

const cacheControl = cacheControlFactory(60 * 60)

export default function trailingSlashes(req, res, next) {
if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS') {
const split = req.url.split('?')
let pathname = split.shift()
if (pathname !== '/' && pathname.endsWith('/')) {
while (pathname.endsWith('/')) {
pathname = pathname.slice(0, pathname.length - 1)
}
let url = pathname
if (split.length) {
url += `?${split.join('?')}`
}
// So it can be cached in the CDN
res.removeHeader('set-cookie')
cacheControl(res)
return res.redirect(301, url)
}
}

next()
}
14 changes: 0 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
"cheerio": "^1.0.0-rc.10",
"classnames": "^2.3.1",
"connect-datadog": "0.0.9",
"connect-slashes": "^1.4.0",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"csurf": "^1.11.0",
Expand Down
16 changes: 16 additions & 0 deletions tests/routing/redirects.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,22 @@ describe('redirects', () => {
expect(res.headers.location).toBe('/ja')
expect(res.headers['cache-control']).toBe('private, no-store')
})
test('trailing slash on languaged homepage should permantently redirect', async () => {
const res = await get('/en/')
expect(res.statusCode).toBe(301)
expect(res.headers.location).toBe('/en')
expect(res.headers['set-cookie']).toBeUndefined()
expect(res.headers['cache-control']).toContain('public')
expect(res.headers['cache-control']).toMatch(/max-age=\d+/)
})
test('trailing slash with query string on languaged homepage should permantently redirect', async () => {
const res = await get('/ja/?foo=bar&bar=foo')
expect(res.statusCode).toBe(301)
expect(res.headers.location).toBe('/ja?foo=bar&bar=foo')
expect(res.headers['set-cookie']).toBeUndefined()
expect(res.headers['cache-control']).toContain('public')
expect(res.headers['cache-control']).toMatch(/max-age=\d+/)
})
})

describe('external redirects', () => {
Expand Down