Skip to content

Commit 2759489

Browse files
chore: warn for problematic user rewrites (#1010)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent 6abca63 commit 2759489

File tree

4 files changed

+113
-12
lines changed

4 files changed

+113
-12
lines changed

src/helpers/redirects.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,21 @@ const generateLocaleRedirects = ({
4242
return redirects
4343
}
4444

45+
export const generateStaticRedirects = ({
46+
netlifyConfig,
47+
nextConfig: { i18n, basePath },
48+
}: {
49+
netlifyConfig: NetlifyConfig
50+
nextConfig: Pick<NextConfig, 'i18n' | 'basePath'>
51+
}) => {
52+
// Static files are in `static`
53+
netlifyConfig.redirects.push({ from: `${basePath}/_next/static/*`, to: `/static/:splat`, status: 200 })
54+
55+
if (i18n) {
56+
netlifyConfig.redirects.push({ from: `${basePath}/:locale/_next/static/*`, to: `/static/:splat`, status: 200 })
57+
}
58+
}
59+
4560
export const generateRedirects = async ({
4661
netlifyConfig,
4762
nextConfig: { i18n, basePath, trailingSlash, appDir },
@@ -95,16 +110,10 @@ export const generateRedirects = async ({
95110
dataRedirects.push(...netlifyRoutesForNextRoute(dataRoute))
96111
})
97112

98-
if (i18n) {
99-
netlifyConfig.redirects.push({ from: `${basePath}/:locale/_next/static/*`, to: `/static/:splat`, status: 200 })
100-
}
101-
102113
const publicFiles = await globby('**/*', { cwd: join(appDir, 'public') })
103114

104115
// This is only used in prod, so dev uses `next dev` directly
105116
netlifyConfig.redirects.push(
106-
// Static files are in `static`
107-
{ from: `${basePath}/_next/static/*`, to: `/static/:splat`, status: 200 },
108117
// API routes always need to be served from the regular function
109118
{
110119
from: `${basePath}/api`,

src/helpers/verification.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
/* eslint-disable max-lines */
12
import { existsSync, promises } from 'fs'
23
import path, { relative } from 'path'
34

4-
import { NetlifyPluginUtils } from '@netlify/build'
5+
import { NetlifyConfig, NetlifyPluginUtils } from '@netlify/build'
56
import { yellowBright, greenBright, blueBright, redBright, reset } from 'chalk'
67
import { async as StreamZip } from 'node-stream-zip'
78
import { outdent } from 'outdent'
@@ -136,3 +137,55 @@ export const checkZipSize = async (file: string, maxSize: number = LAMBDA_MAX_SI
136137
greenBright`\n\nFor more information on fixing this, see ${blueBright`https://ntl.fyi/large-next-functions`}`,
137138
)
138139
}
140+
141+
export const getProblematicUserRewrites = ({
142+
redirects,
143+
basePath,
144+
}: {
145+
redirects: NetlifyConfig['redirects']
146+
basePath: string
147+
}) => {
148+
const userRewrites: NetlifyConfig['redirects'] = []
149+
for (const redirect of redirects) {
150+
// This is the first of the plugin-generated redirects so we can stop checking
151+
if (redirect.from === `${basePath}/_next/static/*` && redirect.to === `/static/:splat` && redirect.status === 200) {
152+
break
153+
}
154+
if (
155+
// Redirects are fine
156+
(redirect.status === 200 || redirect.status === 404) &&
157+
// Rewriting to a function is also fine
158+
!redirect.to.startsWith('/.netlify/') &&
159+
// ...so is proxying
160+
!redirect.to.startsWith('http')
161+
) {
162+
userRewrites.push(redirect)
163+
}
164+
}
165+
return userRewrites
166+
}
167+
168+
export const warnForProblematicUserRewrites = ({
169+
redirects,
170+
basePath,
171+
}: {
172+
redirects: NetlifyConfig['redirects']
173+
basePath: string
174+
}) => {
175+
const userRewrites = getProblematicUserRewrites({ redirects, basePath })
176+
if (userRewrites.length === 0) {
177+
return
178+
}
179+
console.log(
180+
yellowBright(outdent`
181+
You have the following Netlify rewrite${
182+
userRewrites.length === 1 ? '' : 's'
183+
} that might cause conflicts with the Next.js plugin:
184+
185+
${reset(userRewrites.map(({ from, to, status }) => `- ${from} ${to} ${status}`).join('\n'))}
186+
187+
For more information, see https://ntl.fyi/next-rewrites
188+
`),
189+
)
190+
}
191+
/* eslint-enable max-lines */

src/index.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import { restoreCache, saveCache } from './helpers/cache'
77
import { getNextConfig, configureHandlerFunctions } from './helpers/config'
88
import { moveStaticPages, movePublicFiles, patchNextFiles, unpatchNextFiles } from './helpers/files'
99
import { generateFunctions, setupImageFunction, generatePagesResolver } from './helpers/functions'
10-
import { generateRedirects } from './helpers/redirects'
10+
import { generateRedirects, generateStaticRedirects } from './helpers/redirects'
1111
import {
1212
verifyNetlifyBuildVersion,
1313
checkNextSiteHasBuilt,
1414
checkForRootPublish,
1515
checkZipSize,
1616
checkForOldFunctions,
17+
warnForProblematicUserRewrites,
1718
} from './helpers/verification'
1819

1920
const plugin: NetlifyPlugin = {
@@ -73,6 +74,11 @@ const plugin: NetlifyPlugin = {
7374
await moveStaticPages({ target, netlifyConfig, i18n })
7475
}
7576

77+
await generateStaticRedirects({
78+
netlifyConfig,
79+
nextConfig: { basePath, i18n },
80+
})
81+
7682
await setupImageFunction({ constants, imageconfig: images, netlifyConfig, basePath })
7783

7884
await generateRedirects({
@@ -82,18 +88,22 @@ const plugin: NetlifyPlugin = {
8288
},
8389

8490
async onPostBuild({
85-
netlifyConfig,
91+
netlifyConfig: {
92+
build: { publish },
93+
redirects,
94+
},
8695
utils: {
8796
cache,
8897
functions,
8998
build: { failBuild },
9099
},
91100
constants: { FUNCTIONS_DIST },
92101
}) {
93-
await saveCache({ cache, publish: netlifyConfig.build.publish })
102+
await saveCache({ cache, publish })
94103
await checkForOldFunctions({ functions })
95104
await checkZipSize(join(FUNCTIONS_DIST, `${ODB_FUNCTION_NAME}.zip`))
96-
const { basePath } = await getNextConfig({ publish: netlifyConfig.build.publish, failBuild })
105+
const { basePath } = await getNextConfig({ publish, failBuild })
106+
warnForProblematicUserRewrites({ basePath, redirects })
97107
await unpatchNextFiles(basePath)
98108
},
99109
}

test/index.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { writeJSON, unlink, existsSync, readFileSync, copy, ensureDir, readJson } = require('fs-extra')
1+
const { writeJSON, unlink, existsSync, readFileSync, copy, ensureDir, readJson, writeFile } = require('fs-extra')
22
const path = require('path')
33
const process = require('process')
44
const os = require('os')
@@ -12,6 +12,8 @@ const { HANDLER_FUNCTION_NAME, ODB_FUNCTION_NAME } = require('../src/constants')
1212
const { join } = require('pathe')
1313
const { matchMiddleware, stripLocale, matchesRedirect, matchesRewrite } = require('../src/helpers/files')
1414
const { dirname } = require('path')
15+
const { getProblematicUserRewrites } = require('../src/helpers/verification')
16+
const { outdent } = require('outdent')
1517

1618
const FIXTURES_DIR = `${__dirname}/fixtures`
1719
const SAMPLE_PROJECT_DIR = `${__dirname}/../demos/default`
@@ -442,6 +444,33 @@ describe('onPostBuild', () => {
442444

443445
console.log = oldLog
444446
})
447+
448+
test('finds problematic user rewrites', async () => {
449+
await moveNextDist()
450+
const rewrites = getProblematicUserRewrites({
451+
redirects: [
452+
{ from: '/previous', to: '/rewrites-are-a-problem', status: 200 },
453+
{ from: '/api', to: '/.netlify/functions/are-ok', status: 200 },
454+
{ from: '/remote', to: 'http://example.com/proxying/is/ok', status: 200 },
455+
{ from: '/old', to: '/redirects-are-fine' },
456+
{ from: '/*', to: '/404-is-a-problem', status: 404 },
457+
...netlifyConfig.redirects,
458+
],
459+
basePath: '',
460+
})
461+
expect(rewrites).toEqual([
462+
{
463+
from: '/previous',
464+
status: 200,
465+
to: '/rewrites-are-a-problem',
466+
},
467+
{
468+
from: '/*',
469+
status: 404,
470+
to: '/404-is-a-problem',
471+
},
472+
])
473+
})
445474
})
446475

447476
describe('utility functions', () => {

0 commit comments

Comments
 (0)