Skip to content

Commit 6dc8579

Browse files
authored
fix edge runtime asset fetch in pages api (#76750)
### What We discoved an issue that fetch new URL with static assets in Pages API on edge runtime were failing. Located the issue was an introduced in #66534 where we changed the layer of Pages API with edge runtime entries into API layer, where it actually should be another separate layer like middleware. The quick fix is using middleware bundle layer for Pages API edge functions, but middleware is server-only layer which Pages API is not yet, since it doesn't bundle everything and picking up the `react-server` condition. So we have to create a new layer `apiEdge` to minimize the behavior changes and also fix the bug. The test suite was disabled due to node-fetch blocker, but we now we can enable it. Closes NDX-941 Fixes #74385 [slack-ref](https://vercel.slack.com/archives/C07CGAA2RS6/p1740868643335099)
1 parent ead6e95 commit 6dc8579

File tree

5 files changed

+90
-62
lines changed

5 files changed

+90
-62
lines changed

packages/next/src/build/entries.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,10 @@ export function getEdgeServerEntry(opts: {
424424
).toString('base64'),
425425
}
426426

427-
return `next-edge-function-loader?${stringify(loaderParams)}!`
427+
return {
428+
import: `next-edge-function-loader?${stringify(loaderParams)}!`,
429+
layer: WEBPACK_LAYERS.apiEdge,
430+
}
428431
}
429432

430433
const loaderParams: EdgeSSRLoaderQuery = {
@@ -876,7 +879,7 @@ export function finalizeEntrypoint({
876879
switch (compilerType) {
877880
case COMPILER_NAMES.server: {
878881
const layer = isApi
879-
? WEBPACK_LAYERS.api
882+
? WEBPACK_LAYERS.apiNode
880883
: isInstrumentation
881884
? WEBPACK_LAYERS.instrument
882885
: isServerComponent
@@ -895,7 +898,7 @@ export function finalizeEntrypoint({
895898
case COMPILER_NAMES.edgeServer: {
896899
return {
897900
layer: isApi
898-
? WEBPACK_LAYERS.api
901+
? WEBPACK_LAYERS.apiEdge
899902
: isMiddlewareFilename(name) || isInstrumentation
900903
? WEBPACK_LAYERS.middleware
901904
: name.startsWith('pages/')

packages/next/src/build/webpack-config.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,7 @@ export default async function getBaseWebpackConfig(
645645
const apiRoutesLayerLoaders = useSWCLoader
646646
? getSwcLoader({
647647
serverComponents: false,
648-
bundleLayer: WEBPACK_LAYERS.api,
648+
bundleLayer: WEBPACK_LAYERS.apiNode,
649649
})
650650
: defaultLoaders.babel
651651

@@ -1575,15 +1575,20 @@ export default async function getBaseWebpackConfig(
15751575
oneOf: [
15761576
{
15771577
...codeCondition,
1578-
issuerLayer: WEBPACK_LAYERS.api,
1578+
issuerLayer: WEBPACK_LAYERS.apiNode,
1579+
use: apiRoutesLayerLoaders,
1580+
// In Node.js, switch back to normal URL handling.
1581+
// We won't bundle `new URL()` cases in Node.js bundler layer.
15791582
parser: {
1580-
// In Node.js, switch back to normal URL handling.
1581-
// In Edge runtime, we should disable parser.url handling in webpack so URLDependency is not added.
1582-
// Then there's browser code won't be injected into the edge runtime chunk.
1583-
// x-ref: https://github.com/webpack/webpack/blob/d9ce3b1f87e63c809d8a19bbd92257d65922e81f/lib/web/JsonpChunkLoadingRuntimeModule.js#L69
1584-
url: !isEdgeServer,
1583+
url: true,
15851584
},
1585+
},
1586+
{
1587+
...codeCondition,
1588+
issuerLayer: WEBPACK_LAYERS.apiEdge,
15861589
use: apiRoutesLayerLoaders,
1590+
// In Edge runtime, we leave the url handling by default.
1591+
// The new URL assets will be converted into edge assets through assets loader.
15871592
},
15881593
{
15891594
test: codeCondition.test,

packages/next/src/build/webpack/plugins/middleware-plugin.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ function buildWebpackError({
279279

280280
function isInMiddlewareLayer(parser: webpack.javascript.JavascriptParser) {
281281
const layer = parser.state.module?.layer
282-
return layer === WEBPACK_LAYERS.middleware || layer === WEBPACK_LAYERS.api
282+
return layer === WEBPACK_LAYERS.middleware || layer === WEBPACK_LAYERS.apiEdge
283283
}
284284

285285
function isNodeJsModule(moduleName: string) {
@@ -868,7 +868,7 @@ export async function handleWebpackExternalForEdgeRuntime({
868868
}) {
869869
if (
870870
(contextInfo.issuerLayer === WEBPACK_LAYERS.middleware ||
871-
contextInfo.issuerLayer === WEBPACK_LAYERS.api) &&
871+
contextInfo.issuerLayer === WEBPACK_LAYERS.apiEdge) &&
872872
isNodeJsModule(request) &&
873873
!supportedEdgePolyfills.has(request)
874874
) {

packages/next/src/lib/constants.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,13 @@ const WEBPACK_LAYERS_NAMES = {
118118
*/
119119
actionBrowser: 'action-browser',
120120
/**
121-
* The layer for the API routes.
121+
* The Node.js bundle layer for the API routes.
122122
*/
123-
api: 'api',
123+
apiNode: 'api-node',
124+
/**
125+
* The Edge Lite bundle layer for the API routes.
126+
*/
127+
apiEdge: 'api-edge',
124128
/**
125129
* The layer for the middleware code.
126130
*/
@@ -169,7 +173,8 @@ const WEBPACK_LAYERS = {
169173
],
170174
neutralTarget: [
171175
// pages api
172-
WEBPACK_LAYERS_NAMES.api,
176+
WEBPACK_LAYERS_NAMES.apiNode,
177+
WEBPACK_LAYERS_NAMES.apiEdge,
173178
],
174179
clientOnly: [
175180
WEBPACK_LAYERS_NAMES.serverSideRendering,
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,13 @@
1-
import { createNext, FileRef } from 'e2e-utils'
2-
import { NextInstance } from 'e2e-utils'
1+
import { nextTestSetup } from 'e2e-utils'
32
import { fetchViaHTTP, renderViaHTTP } from 'next-test-utils'
43
import path from 'path'
54
import { promises as fs } from 'fs'
65
import { readJson } from 'fs-extra'
76

8-
// TODO: `node-fetch` hangs on some of these tests in Node.js.
9-
// Re-enable when `node-fetch` is dropped.
10-
// See: https://github.com/vercel/next.js/pull/55112
11-
describe.skip('Edge Compiler can import asset assets', () => {
12-
let next: NextInstance
13-
14-
// TODO: remove after this is supported for deploy
15-
if ((global as any).isNextDeploy) {
16-
it('should skip for deploy for now', () => {})
17-
return
18-
}
19-
20-
beforeAll(async () => {
21-
next = await createNext({
22-
files: new FileRef(path.join(__dirname, './app')),
23-
})
7+
describe('Edge Compiler can import asset assets', () => {
8+
const { next, isTurbopack, isNextDeploy } = nextTestSetup({
9+
files: path.join(__dirname, './app'),
2410
})
25-
afterAll(() => next.destroy())
2611

2712
it('allows to fetch a remote URL', async () => {
2813
const response = await fetchViaHTTP(next.url, '/api/edge', {
@@ -66,33 +51,63 @@ describe.skip('Edge Compiler can import asset assets', () => {
6651
})
6752
})
6853

69-
it('extracts all the assets from the bundle', async () => {
70-
const manifestPath = path.join(
71-
next.testDir,
72-
'.next/server/middleware-manifest.json'
73-
)
74-
const manifest = await readJson(manifestPath)
75-
const orderedAssets = manifest.functions['/api/edge'].assets.sort(
76-
(a, z) => {
77-
return String(a.name).localeCompare(z.name)
78-
}
79-
)
54+
// Cannot read the file on deployment
55+
if (!isNextDeploy) {
56+
it('extracts all the assets from the bundle', async () => {
57+
const manifestPath = path.join(
58+
next.testDir,
59+
'.next/server/middleware-manifest.json'
60+
)
61+
const manifest = await readJson(manifestPath)
62+
const orderedAssets = manifest.functions['/api/edge'].assets.sort(
63+
(a, z) => {
64+
return String(a.name).localeCompare(z.name)
65+
}
66+
)
8067

81-
expect(orderedAssets).toMatchObject([
82-
{
83-
name: expect.stringMatching(/^text-file\.[0-9a-f]{16}\.txt$/),
84-
filePath: expect.stringMatching(
85-
/^server\/edge-chunks\/asset_text-file/
86-
),
87-
},
88-
{
89-
name: expect.stringMatching(/^vercel\.[0-9a-f]{16}\.png$/),
90-
filePath: expect.stringMatching(/^server\/edge-chunks\/asset_vercel/),
91-
},
92-
{
93-
name: expect.stringMatching(/^world\.[0-9a-f]{16}\.json/),
94-
filePath: expect.stringMatching(/^server\/edge-chunks\/asset_world/),
95-
},
96-
])
97-
})
68+
if (isTurbopack) {
69+
expect(orderedAssets).toMatchObject([
70+
{
71+
name: expect.stringMatching(
72+
/server\/edge\/assets\/text-file\.[0-9a-f]{8}\.txt$/
73+
),
74+
filePath: expect.stringMatching(/^server\/edge\/assets\/text-file/),
75+
},
76+
{
77+
name: expect.stringMatching(
78+
/^server\/edge\/assets\/vercel\.[0-9a-f]{8}\.png$/
79+
),
80+
filePath: expect.stringMatching(/^server\/edge\/assets\/vercel/),
81+
},
82+
{
83+
name: expect.stringMatching(
84+
/^server\/edge\/assets\/world\.[0-9a-f]{8}\.json/
85+
),
86+
filePath: expect.stringMatching(/^server\/edge\/assets\/world/),
87+
},
88+
])
89+
} else {
90+
expect(orderedAssets).toMatchObject([
91+
{
92+
name: expect.stringMatching(/^text-file\.[0-9a-f]{16}\.txt$/),
93+
filePath: expect.stringMatching(
94+
/^server\/edge-chunks\/asset_text-file/
95+
),
96+
},
97+
{
98+
name: expect.stringMatching(/^vercel\.[0-9a-f]{16}\.png$/),
99+
filePath: expect.stringMatching(
100+
/^server\/edge-chunks\/asset_vercel/
101+
),
102+
},
103+
{
104+
name: expect.stringMatching(/^world\.[0-9a-f]{16}\.json/),
105+
filePath: expect.stringMatching(
106+
/^server\/edge-chunks\/asset_world/
107+
),
108+
},
109+
])
110+
}
111+
})
112+
}
98113
})

0 commit comments

Comments
 (0)