Skip to content

Commit

Permalink
Add required permanent: boolean field to redirect (#10044)
Browse files Browse the repository at this point in the history
* Add permanent field for redirects

* Update printing redirect status code

* Don't add permanent to routes-manifest

Co-authored-by: Joe Haddad <timer150@gmail.com>
  • Loading branch information
ijjk and Timer committed Jan 14, 2020
1 parent c30e542 commit 84264f8
Show file tree
Hide file tree
Showing 8 changed files with 51 additions and 18 deletions.
5 changes: 3 additions & 2 deletions packages/next/build/index.ts
Expand Up @@ -10,6 +10,7 @@ import { pathToRegexp } from 'path-to-regexp'
import { promisify } from 'util'
import formatWebpackMessages from '../client/dev/error-overlay/format-webpack-messages'
import checkCustomRoutes, {
getRedirectStatus,
RouteType,
Redirect,
Rewrite,
Expand All @@ -22,7 +23,6 @@ import { recursiveReadDir } from '../lib/recursive-readdir'
import { verifyTypeScriptSetup } from '../lib/verifyTypeScriptSetup'
import {
BUILD_MANIFEST,
DEFAULT_REDIRECT_STATUS,
EXPORT_DETAIL,
EXPORT_MARKER,
PAGES_MANIFEST,
Expand Down Expand Up @@ -246,7 +246,8 @@ export default async function build(dir: string, conf = null): Promise<void> {
...r,
...(type === 'redirect'
? {
statusCode: r.statusCode || DEFAULT_REDIRECT_STATUS,
statusCode: getRedirectStatus(r as Redirect),
permanent: undefined,
}
: {}),
regex: routeRegex.source,
Expand Down
12 changes: 6 additions & 6 deletions packages/next/build/utils.ts
Expand Up @@ -4,11 +4,14 @@ import textTable from 'next/dist/compiled/text-table'
import path from 'path'
import { isValidElementType } from 'react-is'
import stripAnsi from 'strip-ansi'
import { Redirect, Rewrite } from '../lib/check-custom-routes'
import {
Redirect,
Rewrite,
getRedirectStatus,
} from '../lib/check-custom-routes'
import { SPR_GET_INITIAL_PROPS_CONFLICT } from '../lib/constants'
import prettyBytes from '../lib/pretty-bytes'
import { recursiveReadDir } from '../lib/recursive-readdir'
import { DEFAULT_REDIRECT_STATUS } from '../next-server/lib/constants'
import { getRouteMatcher, getRouteRegex } from '../next-server/lib/router/utils'
import { isDynamicRoute } from '../next-server/lib/router/utils/is-dynamic'
import { findPageFile } from '../server/lib/find-page-file'
Expand Down Expand Up @@ -250,10 +253,7 @@ export function printCustomRoutes({
route.source,
route.destination,
...(isRedirects
? [
((route as Redirect).statusCode ||
DEFAULT_REDIRECT_STATUS) + '',
]
? [getRedirectStatus(route as Redirect) + '']
: []),
]
}),
Expand Down
20 changes: 18 additions & 2 deletions packages/next/lib/check-custom-routes.ts
@@ -1,4 +1,8 @@
import { match as regexpMatch } from 'path-to-regexp'
import {
PERMANENT_REDIRECT_STATUS,
TEMPORARY_REDIRECT_STATUS,
} from '../next-server/lib/constants'

export type Rewrite = {
source: string
Expand All @@ -7,6 +11,7 @@ export type Rewrite = {

export type Redirect = Rewrite & {
statusCode?: number
permanent?: boolean
}

export type Header = {
Expand All @@ -16,6 +21,13 @@ export type Header = {

const allowedStatusCodes = new Set([301, 302, 303, 307, 308])

export function getRedirectStatus(route: Redirect) {
return (
route.statusCode ||
(route.permanent ? PERMANENT_REDIRECT_STATUS : TEMPORARY_REDIRECT_STATUS)
)
}

function checkRedirect(route: Redirect) {
const invalidParts: string[] = []
let hadInvalidStatus: boolean = false
Expand All @@ -24,6 +36,10 @@ function checkRedirect(route: Redirect) {
hadInvalidStatus = true
invalidParts.push(`\`statusCode\` is not undefined or valid statusCode`)
}
if (typeof route.permanent !== 'boolean' && !route.statusCode) {
invalidParts.push(`\`permanent\` is not set to \`true\` or \`false\``)
}

return {
invalidParts,
hadInvalidStatus,
Expand Down Expand Up @@ -72,7 +88,7 @@ export default function checkCustomRoutes(
allowedKeys = new Set([
'source',
'destination',
...(isRedirect ? ['statusCode'] : []),
...(isRedirect ? ['statusCode', 'permanent'] : []),
])
} else {
allowedKeys = new Set(['source', 'headers'])
Expand Down Expand Up @@ -106,7 +122,7 @@ export default function checkCustomRoutes(

if (type === 'redirect') {
const result = checkRedirect(route as Redirect)
hadInvalidStatus = result.hadInvalidStatus
hadInvalidStatus = hadInvalidStatus || result.hadInvalidStatus
invalidParts.push(...result.invalidParts)
}

Expand Down
3 changes: 2 additions & 1 deletion packages/next/next-server/lib/constants.ts
Expand Up @@ -32,4 +32,5 @@ export const IS_BUNDLED_PAGE_REGEX = /^static[/\\][^/\\]+[/\\]pages.*\.js$/
// matches static/<buildid>/pages/:page*.js
export const ROUTE_NAME_REGEX = /^static[/\\][^/\\]+[/\\]pages[/\\](.*)\.js$/
export const SERVERLESS_ROUTE_NAME_REGEX = /^pages[/\\](.*)\.js$/
export const DEFAULT_REDIRECT_STATUS = 307
export const TEMPORARY_REDIRECT_STATUS = 307
export const PERMANENT_REDIRECT_STATUS = 308
5 changes: 2 additions & 3 deletions packages/next/next-server/server/next-server.ts
Expand Up @@ -17,7 +17,6 @@ import {
ROUTES_MANIFEST,
SERVER_DIRECTORY,
SERVERLESS_DIRECTORY,
DEFAULT_REDIRECT_STATUS,
} from '../lib/constants'
import {
getRouteMatcher,
Expand Down Expand Up @@ -50,6 +49,7 @@ import {
Rewrite,
RouteType,
Header,
getRedirectStatus,
} from '../../lib/check-custom-routes'

const getCustomRouteMatcher = pathMatch(true)
Expand Down Expand Up @@ -493,8 +493,7 @@ export default class Server {
})

res.setHeader('Location', updatedDestination)
res.statusCode =
(route as Redirect).statusCode || DEFAULT_REDIRECT_STATUS
res.statusCode = getRedirectStatus(route as Redirect)

// Since IE11 doesn't support the 308 header add backwards
// compatibility using refresh header
Expand Down
6 changes: 5 additions & 1 deletion test/integration/custom-routes/next.config.js
Expand Up @@ -77,10 +77,12 @@ module.exports = {
{
source: '/hello/:id/another',
destination: '/blog/:id',
permanent: false,
},
{
source: '/redirect1',
destination: '/',
permanent: false,
},
{
source: '/redirect2',
Expand All @@ -95,7 +97,7 @@ module.exports = {
{
source: '/redirect4',
destination: '/',
statusCode: 308,
permanent: true,
},
{
source: '/redir-chain1',
Expand All @@ -115,10 +117,12 @@ module.exports = {
{
source: '/to-external',
destination: 'https://google.com',
permanent: false,
},
{
source: '/query-redirect/:section/:name',
destination: '/with-params?first=:section&second=:name',
permanent: false,
},
]
},
Expand Down
2 changes: 1 addition & 1 deletion test/integration/custom-routes/test/index.test.js
Expand Up @@ -65,7 +65,7 @@ const runTests = (isDev = false) => {
expect(res3location).toBe('/')
})

it('should redirect successfully with default statusCode', async () => {
it('should redirect successfully with permanent: false', async () => {
const res = await fetchViaHTTP(appPort, '/redirect1', undefined, {
redirect: 'manual',
})
Expand Down
16 changes: 14 additions & 2 deletions test/integration/invalid-custom-routes/test/index.test.js
Expand Up @@ -28,11 +28,13 @@ const invalidRedirects = [
{
// missing destination
source: '/hello',
permanent: false,
},
{
// invalid source
source: 123,
destination: '/another',
permanent: false,
},
{
// invalid statusCode type
Expand All @@ -46,15 +48,21 @@ const invalidRedirects = [
destination: '/another',
statusCode: 404,
},
{
// invalid permanent value
source: '/hello',
destination: '/another',
permanent: 'yes',
},
]

const invalidRedirectAssertions = (stderr = '') => {
expect(stderr).toContain(
`\`destination\` is missing for route {"source":"/hello"}`
`\`destination\` is missing for route {"source":"/hello","permanent":false}`
)

expect(stderr).toContain(
`\`source\` is not a string for route {"source":123,"destination":"/another"}`
`\`source\` is not a string for route {"source":123,"destination":"/another","permanent":false}`
)

expect(stderr).toContain(
Expand All @@ -65,6 +73,10 @@ const invalidRedirectAssertions = (stderr = '') => {
`\`statusCode\` is not undefined or valid statusCode for route {"source":"/hello","destination":"/another","statusCode":404}`
)

expect(stderr).toContain(
`\`permanent\` is not set to \`true\` or \`false\` for route {"source":"/hello","destination":"/another","permanent":"yes"}`
)

expect(stderr).toContain(
'Valid redirect statusCode values are 301, 302, 303, 307, 308'
)
Expand Down

0 comments on commit 84264f8

Please sign in to comment.