Skip to content

Commit 126fd93

Browse files
authored
Merge branch 'main' into mk/canary-demo
2 parents f2283e9 + 2aa02db commit 126fd93

File tree

4 files changed

+118
-38
lines changed

4 files changed

+118
-38
lines changed

cypress/integration/default/dynamic-routes.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ describe('Dynamic Routing', () => {
4949
cy.request({ url: '/getStaticProps/3/', headers: { 'x-nf-debug-logging': '1' }, failOnStatusCode: false }).then(
5050
(res) => {
5151
expect(res.status).to.eq(404)
52-
expect(res.headers).to.have.property('x-nf-render-mode', 'odb')
52+
expect(res.headers).to.not.have.property('x-nf-render-mode')
5353
expect(res.body).to.contain('Custom 404')
5454
},
5555
)
@@ -102,7 +102,7 @@ describe('Dynamic Routing', () => {
102102
failOnStatusCode: false,
103103
}).then((res) => {
104104
expect(res.status).to.eq(404)
105-
expect(res.headers).to.have.property('x-nf-render-mode', 'odb')
105+
expect(res.headers).to.not.have.property('x-nf-render-mode')
106106
expect(res.body).to.contain('Custom 404')
107107
})
108108
})

packages/runtime/src/helpers/redirects.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
isApiRoute,
1919
redirectsForNextRoute,
2020
redirectsForNextRouteWithData,
21+
redirectsForNext404Route,
2122
routeToDataRoute,
2223
} from './utils'
2324

@@ -186,13 +187,15 @@ const generateDynamicRewrites = ({
186187
basePath,
187188
buildId,
188189
i18n,
190+
is404Isr,
189191
}: {
190192
dynamicRoutes: RoutesManifest['dynamicRoutes']
191193
prerenderedDynamicRoutes: PrerenderManifest['dynamicRoutes']
192194
basePath: string
193195
i18n: NextConfig['i18n']
194196
buildId: string
195197
middleware: Array<string>
198+
is404Isr: boolean
196199
}): {
197200
dynamicRoutesThatMatchMiddleware: Array<string>
198201
dynamicRewrites: NetlifyConfig['redirects']
@@ -206,9 +209,11 @@ const generateDynamicRewrites = ({
206209
if (route.page in prerenderedDynamicRoutes) {
207210
if (matchesMiddleware(middleware, route.page)) {
208211
dynamicRoutesThatMatchMiddleware.push(route.page)
212+
} else if (prerenderedDynamicRoutes[route.page].fallback === false && !is404Isr) {
213+
dynamicRewrites.push(...redirectsForNext404Route({ route: route.page, buildId, basePath, i18n }))
209214
} else {
210215
dynamicRewrites.push(
211-
...redirectsForNextRoute({ buildId, route: route.page, basePath, to: ODB_FUNCTION_PATH, status: 200, i18n }),
216+
...redirectsForNextRoute({ route: route.page, buildId, basePath, to: ODB_FUNCTION_PATH, i18n }),
212217
)
213218
}
214219
} else {
@@ -262,6 +267,10 @@ export const generateRedirects = async ({
262267

263268
const staticRouteEntries = Object.entries(prerenderedStaticRoutes)
264269

270+
const is404Isr = staticRouteEntries.some(
271+
([route, { initialRevalidateSeconds }]) => is404Route(route, i18n) && initialRevalidateSeconds !== false,
272+
)
273+
265274
const routesThatMatchMiddleware: Array<string> = []
266275

267276
const { staticRoutePaths, staticIsrRewrites, staticIsrRoutesThatMatchMiddleware } = generateStaticIsrRewrites({
@@ -294,6 +303,7 @@ export const generateRedirects = async ({
294303
basePath,
295304
buildId,
296305
i18n,
306+
is404Isr,
297307
})
298308
netlifyConfig.redirects.push(...dynamicRewrites)
299309
routesThatMatchMiddleware.push(...dynamicRoutesThatMatchMiddleware)

packages/runtime/src/helpers/utils.ts

+36-4
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,18 @@ export const netlifyRoutesForNextRouteWithData = ({ route, dataRoute }: { route:
7171
export const routeToDataRoute = (route: string, buildId: string, locale?: string) =>
7272
`/_next/data/${buildId}${locale ? `/${locale}` : ''}${route === '/' ? '/index' : route}.json`
7373

74-
const netlifyRoutesForNextRoute = (route: string, buildId: string, i18n?: I18n): Array<string> => {
74+
const netlifyRoutesForNextRoute = (
75+
route: string,
76+
buildId: string,
77+
i18n?: I18n,
78+
): Array<{ redirect: string; locale: string | false }> => {
7579
if (!i18n?.locales?.length) {
76-
return netlifyRoutesForNextRouteWithData({ route, dataRoute: routeToDataRoute(route, buildId) })
80+
return netlifyRoutesForNextRouteWithData({ route, dataRoute: routeToDataRoute(route, buildId) }).map(
81+
(redirect) => ({
82+
redirect,
83+
locale: false,
84+
}),
85+
)
7786
}
7887
const { locales, defaultLocale } = i18n
7988
const routes = []
@@ -86,7 +95,10 @@ const netlifyRoutesForNextRoute = (route: string, buildId: string, i18n?: I18n):
8695
...netlifyRoutesForNextRouteWithData({
8796
route: locale === defaultLocale ? route : `/${locale}${route}`,
8897
dataRoute,
89-
}),
98+
}).map((redirect) => ({
99+
redirect,
100+
locale,
101+
})),
90102
)
91103
})
92104
return routes
@@ -114,13 +126,33 @@ export const redirectsForNextRoute = ({
114126
status?: number
115127
force?: boolean
116128
}): NetlifyConfig['redirects'] =>
117-
netlifyRoutesForNextRoute(route, buildId, i18n).map((redirect) => ({
129+
netlifyRoutesForNextRoute(route, buildId, i18n).map(({ redirect }) => ({
118130
from: `${basePath}${redirect}`,
119131
to,
120132
status,
121133
force,
122134
}))
123135

136+
export const redirectsForNext404Route = ({
137+
route,
138+
buildId,
139+
basePath,
140+
i18n,
141+
force = false,
142+
}: {
143+
route: string
144+
buildId: string
145+
basePath: string
146+
i18n: I18n
147+
force?: boolean
148+
}): NetlifyConfig['redirects'] =>
149+
netlifyRoutesForNextRoute(route, buildId, i18n).map(({ redirect, locale }) => ({
150+
from: `${basePath}${redirect}`,
151+
to: locale ? `${basePath}/server/pages/${locale}/404.html` : `${basePath}/server/pages/404.html`,
152+
status: 404,
153+
force,
154+
}))
155+
124156
export const redirectsForNextRouteWithData = ({
125157
route,
126158
dataRoute,

test/helpers/utils.spec.ts

+69-31
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,39 @@
11
import Chance from 'chance'
22
import { ExperimentalConfig } from 'next/dist/server/config-shared'
3-
import { getCustomImageResponseHeaders, getRemotePatterns, ImagesConfig } from '../../packages/runtime/src/helpers/utils'
3+
import {
4+
getCustomImageResponseHeaders,
5+
getRemotePatterns,
6+
ImagesConfig,
7+
redirectsForNext404Route,
8+
} from '../../packages/runtime/src/helpers/utils'
49

510
const chance = new Chance()
611

712
describe('getCustomImageResponseHeaders', () => {
813
it('returns null when no custom image response headers are found', () => {
9-
const mockHeaders = [{
10-
for: '/test',
11-
values: {
12-
'X-Foo': chance.string()
13-
}
14-
}]
14+
const mockHeaders = [
15+
{
16+
for: '/test',
17+
values: {
18+
'X-Foo': chance.string(),
19+
},
20+
},
21+
]
1522

1623
expect(getCustomImageResponseHeaders(mockHeaders)).toBe(null)
1724
})
1825

1926
it('returns header values when custom image response headers are found', () => {
2027
const mockFooValue = chance.string()
2128

22-
const mockHeaders = [{
23-
for: '/_next/image/',
24-
values: {
25-
'X-Foo': mockFooValue
26-
}
27-
}]
29+
const mockHeaders = [
30+
{
31+
for: '/_next/image/',
32+
values: {
33+
'X-Foo': mockFooValue,
34+
},
35+
},
36+
]
2837

2938
const result = getCustomImageResponseHeaders(mockHeaders)
3039
expect(result).toStrictEqual({
@@ -38,31 +47,23 @@ describe('getRemotePatterns', () => {
3847
let mockImages
3948
beforeEach(() => {
4049
mockExperimentalConfig = {
41-
images: {}
50+
images: {},
4251
} as ExperimentalConfig
43-
52+
4453
mockImages = {
45-
deviceSizes: [
46-
640, 750, 828,
47-
1080, 1200, 1920,
48-
2048, 3840
49-
],
50-
imageSizes: [
51-
16, 32, 48, 64,
52-
96, 128, 256, 384
53-
],
54+
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
55+
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
5456
path: '/_next/image',
5557
loader: 'default',
5658
domains: [],
5759
disableStaticImages: false,
5860
minimumCacheTTL: 60,
59-
formats: [ 'image/avif', 'image/webp' ],
61+
formats: ['image/avif', 'image/webp'],
6062
dangerouslyAllowSVG: false,
6163
contentSecurityPolicy: "script-src 'none'; frame-src 'none'; sandbox;",
6264
unoptimized: false,
63-
remotePatterns: []
65+
remotePatterns: [],
6466
} as ImagesConfig
65-
6667
})
6768

6869
it('returns the remote patterns found under experimental.images', () => {
@@ -73,7 +74,7 @@ describe('getRemotePatterns', () => {
7374
},
7475
]
7576
const result = getRemotePatterns(mockExperimentalConfig, mockImages)
76-
77+
7778
expect(result).toStrictEqual(mockExperimentalConfig.images?.remotePatterns)
7879
})
7980

@@ -85,12 +86,49 @@ describe('getRemotePatterns', () => {
8586
},
8687
]
8788
const result = getRemotePatterns(mockExperimentalConfig, mockImages)
88-
89-
expect(result).toStrictEqual(mockImages.remotePatterns)
89+
90+
expect(result).toStrictEqual(mockImages.remotePatterns)
9091
})
91-
92+
9293
it('returns an empty array', () => {
9394
const result = getRemotePatterns(mockExperimentalConfig, mockImages)
9495
expect(result).toStrictEqual([])
9596
})
9697
})
98+
99+
describe('redirectsForNext404Route', () => {
100+
it('returns static 404 redirects', () => {
101+
const mockRoute = {
102+
route: '/test',
103+
buildId: 'test',
104+
basePath: '',
105+
i18n: null,
106+
}
107+
108+
expect(redirectsForNext404Route(mockRoute)).toStrictEqual([
109+
{ force: false, from: '/_next/data/test/test.json', status: 404, to: '/server/pages/404.html' },
110+
{ force: false, from: '/test', status: 404, to: '/server/pages/404.html' },
111+
])
112+
})
113+
114+
it('returns localised static 404 redirects when i18n locales are provided', () => {
115+
const mockRoute = {
116+
route: '/test',
117+
buildId: 'test',
118+
basePath: '',
119+
i18n: {
120+
defaultLocale: 'en',
121+
locales: ['en', 'es', 'fr'],
122+
},
123+
}
124+
125+
expect(redirectsForNext404Route(mockRoute)).toStrictEqual([
126+
{ force: false, from: '/_next/data/test/en/test.json', status: 404, to: '/server/pages/en/404.html' },
127+
{ force: false, from: '/test', status: 404, to: '/server/pages/en/404.html' },
128+
{ force: false, from: '/_next/data/test/es/test.json', status: 404, to: '/server/pages/es/404.html' },
129+
{ force: false, from: '/es/test', status: 404, to: '/server/pages/es/404.html' },
130+
{ force: false, from: '/_next/data/test/fr/test.json', status: 404, to: '/server/pages/fr/404.html' },
131+
{ force: false, from: '/fr/test', status: 404, to: '/server/pages/fr/404.html' },
132+
])
133+
})
134+
})

0 commit comments

Comments
 (0)