Skip to content

Commit ff9445a

Browse files
committed
chore: changes from review
1 parent 53a0bdd commit ff9445a

File tree

2 files changed

+153
-78
lines changed

2 files changed

+153
-78
lines changed

src/helpers/files.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export const matchesRewrite = (file: string, rewrites: Rewrites): boolean => {
6161
export const getMiddleware = async (publish: string): Promise<Array<string>> => {
6262
const manifestPath = join(publish, 'server', 'middleware-manifest.json')
6363
if (existsSync(manifestPath)) {
64-
const manifest = await readJson(manifestPath)
64+
const manifest = await readJson(manifestPath, { throws: false })
6565
return manifest?.sortedMiddleware ?? []
6666
}
6767
return []

src/helpers/redirects.ts

+152-77
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/* eslint-disable max-lines */
2-
import { NetlifyConfig } from '@netlify/build'
2+
import type { NetlifyConfig } from '@netlify/build'
33
import { yellowBright } from 'chalk'
44
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'
77
import { outdent } from 'outdent'
88
import { join } from 'pathe'
99

@@ -21,7 +21,7 @@ import {
2121
} from './utils'
2222

2323
const matchesMiddleware = (middleware: Array<string>, route: string): boolean =>
24-
middleware?.some((middlewarePath) => route.startsWith(middlewarePath))
24+
middleware.some((middlewarePath) => route.startsWith(middlewarePath))
2525

2626
const 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

Comments
 (0)