1
1
/* eslint-disable max-lines */
2
- import { NetlifyConfig } from '@netlify/build'
2
+ import type { NetlifyConfig } from '@netlify/build'
3
3
import { yellowBright } from 'chalk'
4
4
import { readJSON } from 'fs-extra'
5
- import { NextConfig } from 'next'
6
- import { PrerenderManifest } from 'next/dist/build'
5
+ import type { NextConfig } from 'next'
6
+ import type { PrerenderManifest , SsgRoute } from 'next/dist/build'
7
7
import { outdent } from 'outdent'
8
8
import { join } from 'pathe'
9
9
@@ -21,7 +21,7 @@ import {
21
21
} from './utils'
22
22
23
23
const matchesMiddleware = ( middleware : Array < string > , route : string ) : boolean =>
24
- middleware ? .some ( ( middlewarePath ) => route . startsWith ( middlewarePath ) )
24
+ middleware . some ( ( middlewarePath ) => route . startsWith ( middlewarePath ) )
25
25
26
26
const generateLocaleRedirects = ( {
27
27
i18n,
@@ -71,61 +71,23 @@ export const generateStaticRedirects = ({
71
71
}
72
72
}
73
73
74
- // eslint-disable-next-line max-lines-per-function
75
- export const generateRedirects = async ( {
76
- netlifyConfig,
77
- nextConfig : { i18n, basePath, trailingSlash, appDir } ,
78
- buildId,
79
- } : {
80
- netlifyConfig : NetlifyConfig
81
- nextConfig : Pick < NextConfig , 'i18n' | 'basePath' | 'trailingSlash' | 'appDir' >
82
- buildId : string
83
- } ) => {
84
- const { dynamicRoutes : prerenderedDynamicRoutes , routes : prerenderedStaticRoutes } : PrerenderManifest =
85
- await readJSON ( join ( netlifyConfig . build . publish , 'prerender-manifest.json' ) )
86
-
87
- const { dynamicRoutes, staticRoutes } : RoutesManifest = await readJSON (
88
- join ( netlifyConfig . build . publish , 'routes-manifest.json' ) ,
89
- )
90
-
91
- netlifyConfig . redirects . push (
92
- ...HIDDEN_PATHS . map ( ( path ) => ( {
93
- from : `${ basePath } ${ path } ` ,
94
- to : '/404.html' ,
95
- status : 404 ,
96
- force : true ,
97
- } ) ) ,
98
- )
99
-
100
- if ( i18n && i18n . localeDetection !== false ) {
101
- netlifyConfig . redirects . push ( ...generateLocaleRedirects ( { i18n, basePath, trailingSlash } ) )
102
- }
103
-
104
- // This is only used in prod, so dev uses `next dev` directly
105
- netlifyConfig . redirects . push (
106
- // API routes always need to be served from the regular function
107
- ...getApiRewrites ( basePath ) ,
108
- // Preview mode gets forced to the function, to bypass pre-rendered pages, but static files need to be skipped
109
- ...( await getPreviewRewrites ( { basePath, appDir } ) ) ,
110
- )
111
-
112
- const middleware = await getMiddleware ( netlifyConfig . build . publish )
113
- const routesThatMatchMiddleware = new Set < string > ( )
114
-
74
+ /**
75
+ * Routes that match middleware need to always use the SSR function
76
+ * This generates a rewrite for every middleware in every locale, both with and without a splat
77
+ */
78
+ const generateMiddlewareRewrites = ( { basePath, middleware, i18n, buildId } ) => {
115
79
const handlerRewrite = ( from : string ) => ( {
116
80
from : `${ basePath } ${ from } ` ,
117
81
to : HANDLER_FUNCTION_PATH ,
118
82
status : 200 ,
119
83
} )
120
84
121
- // Routes that match middleware need to always use the SSR function
122
- // This generates a rewrite for every middleware in every locale, both with and without a splat
123
- netlifyConfig . redirects . push (
124
- ...middleware
85
+ return (
86
+ middleware
125
87
. map ( ( route ) => {
126
88
const unlocalized = [ handlerRewrite ( `${ route } ` ) , handlerRewrite ( `${ route } /*` ) ]
127
89
if ( i18n ?. locales ?. length > 0 ) {
128
- const localized = i18n ? .locales ? .map ( ( locale ) => [
90
+ const localized = i18n . locales . map ( ( locale ) => [
129
91
handlerRewrite ( `/${ locale } ${ route } ` ) ,
130
92
handlerRewrite ( `/${ locale } ${ route } /*` ) ,
131
93
handlerRewrite ( `/_next/data/${ buildId } /${ locale } ${ route } /*` ) ,
@@ -136,14 +98,26 @@ export const generateRedirects = async ({
136
98
return [ ...unlocalized , handlerRewrite ( `/_next/data/${ buildId } ${ route } /*` ) ]
137
99
} )
138
100
// Flatten the array of arrays. Can't use flatMap as it might be 2 levels deep
139
- . flat ( 2 ) ,
101
+ . flat ( 2 )
140
102
)
103
+ }
141
104
142
- const staticRouteEntries = Object . entries ( prerenderedStaticRoutes )
143
-
105
+ const generateStaticIsrRewrites = ( {
106
+ staticRouteEntries,
107
+ basePath,
108
+ i18n,
109
+ buildId,
110
+ middleware,
111
+ } : {
112
+ staticRouteEntries : Array < [ string , SsgRoute ] >
113
+ basePath : string
114
+ i18n : NextConfig [ 'i18n' ]
115
+ buildId : string
116
+ middleware : Array < string >
117
+ } ) => {
118
+ const staticIsrRoutesThatMatchMiddleware : Array < string > = [ ]
144
119
const staticRoutePaths = new Set < string > ( )
145
-
146
- // First add all static ISR routes
120
+ const staticIsrRewrites : NetlifyConfig [ 'redirects' ] = [ ]
147
121
staticRouteEntries . forEach ( ( [ route , { initialRevalidateSeconds } ] ) => {
148
122
if ( isApiRoute ( route ) ) {
149
123
return
@@ -159,9 +133,9 @@ export const generateRedirects = async ({
159
133
route = route . slice ( i18n . defaultLocale . length + 1 )
160
134
staticRoutePaths . add ( route )
161
135
if ( matchesMiddleware ( middleware , route ) ) {
162
- routesThatMatchMiddleware . add ( route )
136
+ staticIsrRoutesThatMatchMiddleware . push ( route )
163
137
}
164
- netlifyConfig . redirects . push (
138
+ staticIsrRewrites . push (
165
139
...redirectsForNextRouteWithData ( {
166
140
route,
167
141
dataRoute : routeToDataRoute ( route , buildId , i18n . defaultLocale ) ,
@@ -172,45 +146,146 @@ export const generateRedirects = async ({
172
146
)
173
147
} else if ( matchesMiddleware ( middleware , route ) ) {
174
148
// Routes that match middleware can't use the ODB
175
- routesThatMatchMiddleware . add ( route )
149
+ staticIsrRoutesThatMatchMiddleware . push ( route )
176
150
} else {
177
151
// ISR routes use the ODB handler
178
- netlifyConfig . redirects . push (
152
+ staticIsrRewrites . push (
179
153
// No i18n, because the route is already localized
180
154
...redirectsForNextRoute ( { route, basePath, to : ODB_FUNCTION_PATH , force : true , buildId, i18n : null } ) ,
181
155
)
182
156
}
183
157
} )
184
- // Add rewrites for all static SSR routes. This is Next 12+
185
- staticRoutes ?. forEach ( ( route ) => {
186
- if ( staticRoutePaths . has ( route . page ) || isApiRoute ( route . page ) ) {
187
- // Prerendered static routes are either handled by the CDN or are ISR
188
- return
189
- }
190
- netlifyConfig . redirects . push (
191
- ...redirectsForNextRoute ( { route : route . page , buildId, basePath, to : HANDLER_FUNCTION_PATH , i18n } ) ,
192
- )
193
- } )
194
- // Add rewrites for all dynamic routes (both SSR and ISR)
158
+
159
+ return {
160
+ staticRoutePaths,
161
+ staticIsrRoutesThatMatchMiddleware,
162
+ staticIsrRewrites,
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Generate rewrites for all dynamic routes
168
+ */
169
+ const generateDynamicRewrites = ( {
170
+ dynamicRoutes,
171
+ prerenderedDynamicRoutes,
172
+ middleware,
173
+ basePath,
174
+ buildId,
175
+ i18n,
176
+ } : {
177
+ dynamicRoutes : RoutesManifest [ 'dynamicRoutes' ]
178
+ prerenderedDynamicRoutes : PrerenderManifest [ 'dynamicRoutes' ]
179
+ basePath : string
180
+ i18n : NextConfig [ 'i18n' ]
181
+ buildId : string
182
+ middleware : Array < string >
183
+ } ) => {
184
+ const dynamicRewrites : NetlifyConfig [ 'redirects' ] = [ ]
185
+ const dynamicRoutesThatMatchMiddleware : Array < string > = [ ]
195
186
dynamicRoutes . forEach ( ( route ) => {
196
187
if ( isApiRoute ( route . page ) ) {
197
188
return
198
189
}
199
190
if ( route . page in prerenderedDynamicRoutes ) {
200
191
if ( matchesMiddleware ( middleware , route . page ) ) {
201
- routesThatMatchMiddleware . add ( route . page )
192
+ dynamicRoutesThatMatchMiddleware . push ( route . page )
202
193
} else {
203
- netlifyConfig . redirects . push (
194
+ dynamicRewrites . push (
204
195
...redirectsForNextRoute ( { buildId, route : route . page , basePath, to : ODB_FUNCTION_PATH , status : 200 , i18n } ) ,
205
196
)
206
197
}
207
198
} else {
208
199
// If the route isn't prerendered, it's SSR
209
- netlifyConfig . redirects . push (
200
+ dynamicRewrites . push (
210
201
...redirectsForNextRoute ( { route : route . page , buildId, basePath, to : HANDLER_FUNCTION_PATH , i18n } ) ,
211
202
)
212
203
}
213
204
} )
205
+ return {
206
+ dynamicRoutesThatMatchMiddleware,
207
+ dynamicRewrites,
208
+ }
209
+ }
210
+
211
+ export const generateRedirects = async ( {
212
+ netlifyConfig,
213
+ nextConfig : { i18n, basePath, trailingSlash, appDir } ,
214
+ buildId,
215
+ } : {
216
+ netlifyConfig : NetlifyConfig
217
+ nextConfig : Pick < NextConfig , 'i18n' | 'basePath' | 'trailingSlash' | 'appDir' >
218
+ buildId : string
219
+ } ) => {
220
+ const { dynamicRoutes : prerenderedDynamicRoutes , routes : prerenderedStaticRoutes } : PrerenderManifest =
221
+ await readJSON ( join ( netlifyConfig . build . publish , 'prerender-manifest.json' ) )
222
+
223
+ const { dynamicRoutes, staticRoutes } : RoutesManifest = await readJSON (
224
+ join ( netlifyConfig . build . publish , 'routes-manifest.json' ) ,
225
+ )
226
+
227
+ netlifyConfig . redirects . push (
228
+ ...HIDDEN_PATHS . map ( ( path ) => ( {
229
+ from : `${ basePath } ${ path } ` ,
230
+ to : '/404.html' ,
231
+ status : 404 ,
232
+ force : true ,
233
+ } ) ) ,
234
+ )
235
+
236
+ if ( i18n && i18n . localeDetection !== false ) {
237
+ netlifyConfig . redirects . push ( ...generateLocaleRedirects ( { i18n, basePath, trailingSlash } ) )
238
+ }
239
+
240
+ // This is only used in prod, so dev uses `next dev` directly
241
+ netlifyConfig . redirects . push (
242
+ // API routes always need to be served from the regular function
243
+ ...getApiRewrites ( basePath ) ,
244
+ // Preview mode gets forced to the function, to bypass pre-rendered pages, but static files need to be skipped
245
+ ...( await getPreviewRewrites ( { basePath, appDir } ) ) ,
246
+ )
247
+
248
+ const middleware = await getMiddleware ( netlifyConfig . build . publish )
249
+
250
+ netlifyConfig . redirects . push ( ...generateMiddlewareRewrites ( { basePath, i18n, middleware, buildId } ) )
251
+
252
+ const staticRouteEntries = Object . entries ( prerenderedStaticRoutes )
253
+
254
+ const routesThatMatchMiddleware : Array < string > = [ ]
255
+
256
+ const { staticRoutePaths, staticIsrRewrites, staticIsrRoutesThatMatchMiddleware } = generateStaticIsrRewrites ( {
257
+ staticRouteEntries,
258
+ basePath,
259
+ i18n,
260
+ buildId,
261
+ middleware,
262
+ } )
263
+
264
+ routesThatMatchMiddleware . push ( ...staticIsrRoutesThatMatchMiddleware )
265
+
266
+ netlifyConfig . redirects . push ( ...staticIsrRewrites )
267
+
268
+ // Add rewrites for all static SSR routes. This is Next 12+
269
+ staticRoutes ?. forEach ( ( route ) => {
270
+ if ( staticRoutePaths . has ( route . page ) || isApiRoute ( route . page ) ) {
271
+ // Prerendered static routes are either handled by the CDN or are ISR
272
+ return
273
+ }
274
+ netlifyConfig . redirects . push (
275
+ ...redirectsForNextRoute ( { route : route . page , buildId, basePath, to : HANDLER_FUNCTION_PATH , i18n } ) ,
276
+ )
277
+ } )
278
+ // Add rewrites for all dynamic routes (both SSR and ISR)
279
+ const { dynamicRewrites, dynamicRoutesThatMatchMiddleware } = generateDynamicRewrites ( {
280
+ dynamicRoutes,
281
+ prerenderedDynamicRoutes,
282
+ middleware,
283
+ basePath,
284
+ buildId,
285
+ i18n,
286
+ } )
287
+ netlifyConfig . redirects . push ( ...dynamicRewrites )
288
+ routesThatMatchMiddleware . push ( ...dynamicRoutesThatMatchMiddleware )
214
289
215
290
// Final fallback
216
291
netlifyConfig . redirects . push ( {
@@ -219,15 +294,15 @@ export const generateRedirects = async ({
219
294
status : 200 ,
220
295
} )
221
296
222
- const middlewareMatches = routesThatMatchMiddleware . size
297
+ const middlewareMatches = new Set ( routesThatMatchMiddleware ) . size
223
298
if ( middlewareMatches > 0 ) {
224
299
console . log (
225
300
yellowBright ( outdent `
226
301
There ${
227
302
middlewareMatches === 1
228
- ? `is one statically-generated or ISR route`
229
- : `are ${ middlewareMatches } statically-generated or ISR routes`
230
- } that match a middleware function, which means they will always be served from the SSR function and will not use ISR or be served from the CDN.
303
+ ? `is one statically-generated or ISR route that matches `
304
+ : `are ${ middlewareMatches } statically-generated or ISR routes that match `
305
+ } a middleware function. Matched routes will always be served from the SSR function and will not use ISR or be served from the CDN.
231
306
If this was not intended, ensure that your middleware only matches routes that you intend to use SSR.
232
307
` ) ,
233
308
)
0 commit comments