11/* eslint-disable max-lines */
2- import { NetlifyConfig } from '@netlify/build'
2+ import type { NetlifyConfig } from '@netlify/build'
33import { yellowBright } from 'chalk'
44import { 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'
77import { outdent } from 'outdent'
88import { join } from 'pathe'
99
@@ -21,7 +21,7 @@ import {
2121} from './utils'
2222
2323const matchesMiddleware = ( middleware : Array < string > , route : string ) : boolean =>
24- middleware ? .some ( ( middlewarePath ) => route . startsWith ( middlewarePath ) )
24+ middleware . some ( ( middlewarePath ) => route . startsWith ( middlewarePath ) )
2525
2626const generateLocaleRedirects = ( {
2727 i18n,
@@ -71,61 +71,23 @@ export const generateStaticRedirects = ({
7171 }
7272}
7373
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 } ) => {
11579 const handlerRewrite = ( from : string ) => ( {
11680 from : `${ basePath } ${ from } ` ,
11781 to : HANDLER_FUNCTION_PATH ,
11882 status : 200 ,
11983 } )
12084
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
12587 . map ( ( route ) => {
12688 const unlocalized = [ handlerRewrite ( `${ route } ` ) , handlerRewrite ( `${ route } /*` ) ]
12789 if ( i18n ?. locales ?. length > 0 ) {
128- const localized = i18n ? .locales ? .map ( ( locale ) => [
90+ const localized = i18n . locales . map ( ( locale ) => [
12991 handlerRewrite ( `/${ locale } ${ route } ` ) ,
13092 handlerRewrite ( `/${ locale } ${ route } /*` ) ,
13193 handlerRewrite ( `/_next/data/${ buildId } /${ locale } ${ route } /*` ) ,
@@ -136,14 +98,26 @@ export const generateRedirects = async ({
13698 return [ ...unlocalized , handlerRewrite ( `/_next/data/${ buildId } ${ route } /*` ) ]
13799 } )
138100 // Flatten the array of arrays. Can't use flatMap as it might be 2 levels deep
139- . flat ( 2 ) ,
101+ . flat ( 2 )
140102 )
103+ }
141104
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 > = [ ]
144119 const staticRoutePaths = new Set < string > ( )
145-
146- // First add all static ISR routes
120+ const staticIsrRewrites : NetlifyConfig [ 'redirects' ] = [ ]
147121 staticRouteEntries . forEach ( ( [ route , { initialRevalidateSeconds } ] ) => {
148122 if ( isApiRoute ( route ) ) {
149123 return
@@ -159,9 +133,9 @@ export const generateRedirects = async ({
159133 route = route . slice ( i18n . defaultLocale . length + 1 )
160134 staticRoutePaths . add ( route )
161135 if ( matchesMiddleware ( middleware , route ) ) {
162- routesThatMatchMiddleware . add ( route )
136+ staticIsrRoutesThatMatchMiddleware . push ( route )
163137 }
164- netlifyConfig . redirects . push (
138+ staticIsrRewrites . push (
165139 ...redirectsForNextRouteWithData ( {
166140 route,
167141 dataRoute : routeToDataRoute ( route , buildId , i18n . defaultLocale ) ,
@@ -172,45 +146,146 @@ export const generateRedirects = async ({
172146 )
173147 } else if ( matchesMiddleware ( middleware , route ) ) {
174148 // Routes that match middleware can't use the ODB
175- routesThatMatchMiddleware . add ( route )
149+ staticIsrRoutesThatMatchMiddleware . push ( route )
176150 } else {
177151 // ISR routes use the ODB handler
178- netlifyConfig . redirects . push (
152+ staticIsrRewrites . push (
179153 // No i18n, because the route is already localized
180154 ...redirectsForNextRoute ( { route, basePath, to : ODB_FUNCTION_PATH , force : true , buildId, i18n : null } ) ,
181155 )
182156 }
183157 } )
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 > = [ ]
195186 dynamicRoutes . forEach ( ( route ) => {
196187 if ( isApiRoute ( route . page ) ) {
197188 return
198189 }
199190 if ( route . page in prerenderedDynamicRoutes ) {
200191 if ( matchesMiddleware ( middleware , route . page ) ) {
201- routesThatMatchMiddleware . add ( route . page )
192+ dynamicRoutesThatMatchMiddleware . push ( route . page )
202193 } else {
203- netlifyConfig . redirects . push (
194+ dynamicRewrites . push (
204195 ...redirectsForNextRoute ( { buildId, route : route . page , basePath, to : ODB_FUNCTION_PATH , status : 200 , i18n } ) ,
205196 )
206197 }
207198 } else {
208199 // If the route isn't prerendered, it's SSR
209- netlifyConfig . redirects . push (
200+ dynamicRewrites . push (
210201 ...redirectsForNextRoute ( { route : route . page , buildId, basePath, to : HANDLER_FUNCTION_PATH , i18n } ) ,
211202 )
212203 }
213204 } )
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 )
214289
215290 // Final fallback
216291 netlifyConfig . redirects . push ( {
@@ -219,15 +294,15 @@ export const generateRedirects = async ({
219294 status : 200 ,
220295 } )
221296
222- const middlewareMatches = routesThatMatchMiddleware . size
297+ const middlewareMatches = new Set ( routesThatMatchMiddleware ) . size
223298 if ( middlewareMatches > 0 ) {
224299 console . log (
225300 yellowBright ( outdent `
226301 There ${
227302 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.
231306 If this was not intended, ensure that your middleware only matches routes that you intend to use SSR.
232307 ` ) ,
233308 )
0 commit comments