Skip to content

Commit ba036fb

Browse files
authored
fix: use native path matching for prebundled react conditional (#2206)
* fix: use native path matching for app routes * chore: re-enable custom server tests and add react tests * chore: update tests to work on windows
1 parent d4f5d93 commit ba036fb

File tree

2 files changed

+105
-13
lines changed

2 files changed

+105
-13
lines changed

packages/runtime/src/templates/server.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import {
1212
localizeRoute,
1313
localizeDataRoute,
1414
unlocalizeRoute,
15-
joinPaths,
1615
} from './handlerUtils'
1716

1817
interface NetlifyConfig {
@@ -78,12 +77,11 @@ const getNetlifyNextServer = (NextServer: NextServerType) => {
7877
// doing what they do in https://github.com/vercel/vercel/blob/1663db7ca34d3dd99b57994f801fb30b72fbd2f3/packages/next/src/server-build.ts#L576-L580
7978
private netlifyPrebundleReact(path: string) {
8079
const routesManifest = this.getRoutesManifest?.()
81-
const appPathsManifest = this.getAppPathsManifest?.()
80+
const appPathsRoutes = this.getAppPathRoutes?.()
8281

8382
const routes = routesManifest && [...routesManifest.staticRoutes, ...routesManifest.dynamicRoutes]
84-
const matchedRoute = routes?.find((route) => new RegExp(route.regex).test(path.split('?')[0]))
85-
const isAppRoute =
86-
appPathsManifest && matchedRoute ? appPathsManifest[joinPaths(matchedRoute.page, 'page')] : false
83+
const matchedRoute = routes?.find((route) => new RegExp(route.regex).test(new URL(path, 'http://n').pathname))
84+
const isAppRoute = appPathsRoutes && matchedRoute ? appPathsRoutes[matchedRoute.page] : false
8785

8886
if (isAppRoute) {
8987
// app routes should use prebundled React

test/templates/server.spec.ts

+102-8
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,50 @@ jest.mock(
6565
{ virtual: true },
6666
)
6767

68+
jest.mock(
69+
'routes-manifest.json',
70+
() => ({
71+
dynamicRoutes: [
72+
{
73+
page: '/posts/[title]',
74+
regex: '^/posts/([^/]+?)(?:/)?$',
75+
routeKeys: {
76+
nxtPtitle: 'nxtPtitle',
77+
},
78+
namedRegex: '^/posts/(?<nxtPtitle>[^/]+?)(?:/)?$',
79+
},
80+
{
81+
page: '/blog/[author]/[slug]',
82+
regex: '^/blog/([^/]+?)/([^/]+?)(?:/)?$',
83+
routeKeys: {
84+
nxtPauthor: 'nxtPauthor',
85+
nxtPslug: 'nxtPslug',
86+
},
87+
namedRegex: '^/blog/(?<nxtPauthor>[^/]+?)/(?<nxtPslug>[^/]+?)(?:/)?$',
88+
},
89+
],
90+
staticRoutes: [
91+
{
92+
page: '/non-i18n/with-revalidate',
93+
regex: '^/non-i18n/with-revalidate(?:/)?$',
94+
routeKeys: {},
95+
namedRegex: '^/non-i18n/with-revalidate(?:/)?$',
96+
},
97+
{
98+
page: '/i18n/with-revalidate',
99+
regex: '^/i18n/with-revalidate(?:/)?$',
100+
routeKeys: {},
101+
namedRegex: '^/i18n/with-revalidate(?:/)?$',
102+
},
103+
],
104+
}),
105+
{ virtual: true },
106+
)
107+
108+
const appPathsManifest = {
109+
'/blog/(test)/[author]/[slug]/page': 'app/blog/[author]/[slug]/page.js',
110+
}
111+
68112
let NetlifyNextServer: NetlifyNextServerType
69113
beforeAll(() => {
70114
const NextServer: NextServerType = require(getServerFile(__dirname, false)).default
@@ -76,12 +120,14 @@ beforeAll(() => {
76120
this.buildId = mockBuildId
77121
this.nextConfig = nextOptions.conf
78122
this.netlifyConfig = netlifyConfig
123+
this.renderOpts = { previewProps: {} }
124+
this.appPathsManifest = appPathsManifest
79125
}
80126
Object.setPrototypeOf(NetlifyNextServer, MockNetlifyNextServerConstructor)
81127
})
82128

83129
describe('the netlify next server', () => {
84-
it.skip('does not revalidate a request without an `x-prerender-revalidate` header', async () => {
130+
it('does not revalidate a request without an `x-prerender-revalidate` header', async () => {
85131
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, { ...mockTokenConfig })
86132
const requestHandler = netlifyNextServer.getRequestHandler()
87133

@@ -92,7 +138,7 @@ describe('the netlify next server', () => {
92138
expect(mockedApiFetch).not.toHaveBeenCalled()
93139
})
94140

95-
it.skip('revalidates a static non-i18n route with an `x-prerender-revalidate` header', async () => {
141+
it('revalidates a static non-i18n route with an `x-prerender-revalidate` header', async () => {
96142
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, { ...mockTokenConfig })
97143
const requestHandler = netlifyNextServer.getRequestHandler()
98144

@@ -112,7 +158,7 @@ describe('the netlify next server', () => {
112158
)
113159
})
114160

115-
it.skip('revalidates a static i18n route with an `x-prerender-revalidate` header', async () => {
161+
it('revalidates a static i18n route with an `x-prerender-revalidate` header', async () => {
116162
const netlifyNextServer = new NetlifyNextServer({ conf: { ...mocki18nConfig } }, { ...mockTokenConfig })
117163
const requestHandler = netlifyNextServer.getRequestHandler()
118164

@@ -132,7 +178,7 @@ describe('the netlify next server', () => {
132178
)
133179
})
134180

135-
it.skip('revalidates a dynamic non-i18n route with an `x-prerender-revalidate` header', async () => {
181+
it('revalidates a dynamic non-i18n route with an `x-prerender-revalidate` header', async () => {
136182
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, { ...mockTokenConfig })
137183
const requestHandler = netlifyNextServer.getRequestHandler()
138184

@@ -152,7 +198,7 @@ describe('the netlify next server', () => {
152198
)
153199
})
154200

155-
it.skip('revalidates a dynamic i18n route with an `x-prerender-revalidate` header', async () => {
201+
it('revalidates a dynamic i18n route with an `x-prerender-revalidate` header', async () => {
156202
const netlifyNextServer = new NetlifyNextServer({ conf: { ...mocki18nConfig } }, { ...mockTokenConfig })
157203
const requestHandler = netlifyNextServer.getRequestHandler()
158204

@@ -172,7 +218,7 @@ describe('the netlify next server', () => {
172218
)
173219
})
174220

175-
it.skip('throws an error when route is not found in the manifest', async () => {
221+
it('throws an error when route is not found in the manifest', async () => {
176222
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, mockTokenConfig)
177223
const requestHandler = netlifyNextServer.getRequestHandler()
178224

@@ -187,7 +233,7 @@ describe('the netlify next server', () => {
187233
)
188234
})
189235

190-
it.skip('throws an error when paths are not found by the API', async () => {
236+
it('throws an error when paths are not found by the API', async () => {
191237
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, mockTokenConfig)
192238
const requestHandler = netlifyNextServer.getRequestHandler()
193239

@@ -203,7 +249,7 @@ describe('the netlify next server', () => {
203249
)
204250
})
205251

206-
it.skip('throws an error when the revalidate API is unreachable', async () => {
252+
it('throws an error when the revalidate API is unreachable', async () => {
207253
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, mockTokenConfig)
208254
const requestHandler = netlifyNextServer.getRequestHandler()
209255

@@ -218,4 +264,52 @@ describe('the netlify next server', () => {
218264
'Unable to connect',
219265
)
220266
})
267+
268+
it('resolves react as normal for pages routes', async () => {
269+
const netlifyNextServer = new NetlifyNextServer({ conf: {} }, {})
270+
const requestHandler = netlifyNextServer.getRequestHandler()
271+
272+
const { req: mockReq, res: mockRes } = createRequestResponseMocks({
273+
url: '/posts/hello',
274+
})
275+
276+
// @ts-expect-error - Types are incorrect for `MockedResponse`
277+
await requestHandler(new NodeNextRequest(mockReq), new NodeNextResponse(mockRes))
278+
279+
// eslint-disable-next-line no-underscore-dangle
280+
expect(process.env.__NEXT_PRIVATE_PREBUNDLED_REACT).toBe('')
281+
})
282+
283+
it('resolves the prebundled react version for app routes', async () => {
284+
const netlifyNextServer = new NetlifyNextServer({ conf: { experimental: { appDir: true } } }, {})
285+
const requestHandler = netlifyNextServer.getRequestHandler()
286+
287+
const { req: mockReq, res: mockRes } = createRequestResponseMocks({
288+
url: '/blog/rob/hello',
289+
})
290+
291+
// @ts-expect-error - Types are incorrect for `MockedResponse`
292+
await requestHandler(new NodeNextRequest(mockReq), new NodeNextResponse(mockRes))
293+
294+
// eslint-disable-next-line no-underscore-dangle
295+
expect(process.env.__NEXT_PRIVATE_PREBUNDLED_REACT).toBe('next')
296+
})
297+
298+
it('resolves the experimental prebundled react version for app routes with server actions', async () => {
299+
const netlifyNextServer = new NetlifyNextServer(
300+
{ conf: { experimental: { appDir: true, serverActions: true } } },
301+
{},
302+
)
303+
const requestHandler = netlifyNextServer.getRequestHandler()
304+
305+
const { req: mockReq, res: mockRes } = createRequestResponseMocks({
306+
url: '/blog/rob/hello',
307+
})
308+
309+
// @ts-expect-error - Types are incorrect for `MockedResponse`
310+
await requestHandler(new NodeNextRequest(mockReq), new NodeNextResponse(mockRes))
311+
312+
// eslint-disable-next-line no-underscore-dangle
313+
expect(process.env.__NEXT_PRIVATE_PREBUNDLED_REACT).toBe('experimental')
314+
})
221315
})

0 commit comments

Comments
 (0)