Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/build/content/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export const copyNextServerCode = async (ctx: PluginContext): Promise<void> => {
`server/*`,
`server/chunks/**/*`,
`server/edge-chunks/**/*`,
`server/edge/chunks/**/*`,
`server/edge/**/*`,
Copy link
Contributor Author

@pieh pieh Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is for:

fix: bundle edge-runtime assets for turbopack builds

we do need to bundle edge/assets at least (it's needed for @vercel/og / next/og) support

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if it would be preferred to add server/edge/assets/**/* to the list instead of editing the server/edge/chunks entry

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debugging this took quite a while as original error was looking like so

stderr | tests/integration/wasm.test.ts > 'wasm-src' > should work in app route
 ⨯ Error: failed to pipe response
    at ignore-listed frames {
  [cause]: TypeError: fetch failed
      at ignore-listed frames {
    [cause]: Error: invalid method
        at ignore-listed frames
  }
}

It took peeling few layers to understand what's wrong and few dead ends along the way. Generally all those files are recomended to be bundled. In initial PR for turbopack support I also attempted to be conservative with what we bundle and this is how we ended up with just edge/chunks. I think this shown me that there are just cases that I don't know about and that debugging them is painful.

This of course is tradeoff between supporting cases we potentially don't know about today (other than @vercel/og case) vs potential of getting into lambda too large cases. At least latter ones we have some ideas to make easier to debug if we learn that we start bundling too much

`server/+(app|pages)/**/*.js`,
],
{
Expand Down Expand Up @@ -291,6 +291,8 @@ async function patchNextModules(
export const copyNextDependencies = async (ctx: PluginContext): Promise<void> => {
await tracer.withActiveSpan('copyNextDependencies', async () => {
const entries = await readdir(ctx.standaloneDir)
const filter = ctx.constants.IS_LOCAL ? undefined : nodeModulesFilter

const promises: Promise<void>[] = entries.map(async (entry) => {
// copy all except the distDir (.next) folder as this is handled in a separate function
// this will include the node_modules folder as well
Expand All @@ -299,7 +301,6 @@ export const copyNextDependencies = async (ctx: PluginContext): Promise<void> =>
}
const src = join(ctx.standaloneDir, entry)
const dest = join(ctx.serverHandlerDir, entry)
const filter = ctx.constants.IS_LOCAL ? undefined : nodeModulesFilter
await cp(src, dest, {
recursive: true,
verbatimSymlinks: true,
Expand All @@ -321,7 +322,7 @@ export const copyNextDependencies = async (ctx: PluginContext): Promise<void> =>
// see: https://github.com/vercel/next.js/issues/50072
if (existsSync(rootSrcDir) && ctx.standaloneRootDir !== ctx.standaloneDir) {
promises.push(
cp(rootSrcDir, rootDestDir, { recursive: true, verbatimSymlinks: true }).then(() =>
cp(rootSrcDir, rootDestDir, { recursive: true, verbatimSymlinks: true, filter }).then(() =>
Copy link
Contributor Author

@pieh pieh Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is for:

fix: exclude musl binaries from function bundle when building on Netlify with pnpm monorepos

previous fix only applied to non-monorepos - this applies it in morepos as well

recreateNodeModuleSymlinks(resolve('node_modules'), rootDestDir),
),
)
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/cli-before-regional-blobs-support.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ test('should serve 404 page when requesting non existing page (no matching route
const headers = response?.headers() || {}
expect(response?.status()).toBe(404)

expect(await page.textContent('h1')).toBe('404')
await expect(page.locator('h1')).toHaveText('404')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shared for all page.textContent replacements - it's discouraged and deprecated - see https://playwright.dev/docs/api/class-page#page-text-content

I thought this was causing problems in at least some of our tests. This ended up not being problem, but as I already migrated away from it, might as well keep it in this PR I think


// https://github.com/vercel/next.js/pull/69802 made changes to returned cache-control header,
// after that (14.2.10 and canary.147) 404 pages would have `private` directive, before that it
Expand Down
10 changes: 10 additions & 0 deletions tests/e2e/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,11 @@ for (const { expectedRuntime, isNodeMiddleware, label, testWithSwitchableMiddlew
const pageResponse = await page.goto(`${edgeOrNodeMiddlewarePages.url}/link`)
expect(await pageResponse?.headerValue('x-runtime')).toEqual(expectedRuntime)

// wait for hydration to finish before doing client navigation
await expect(page.getByTestId('hydration')).toHaveText('hydrated', {
timeout: 10_000,
})

Comment on lines +249 to +253
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seemed to be a problem where test was clicking links too fast (before hydration happened) so the rest of the test expecting next/link navigation was being very flaky. I did add useEffect in fixture to allow tests to wait for hydration to happen here

await page.evaluate(() => {
// set some value to window to check later if browser did reload and lost this state
;(window as ExtendedWindow).didReload = false
Expand Down Expand Up @@ -305,6 +310,11 @@ for (const { expectedRuntime, isNodeMiddleware, label, testWithSwitchableMiddlew
)
expect(await pageResponse?.headerValue('x-runtime')).toEqual(expectedRuntime)

// wait for hydration to finish before doing client navigation
await expect(page.getByTestId('hydration')).toHaveText('hydrated', {
timeout: 10_000,
})

await page.evaluate(() => {
// set some value to window to check later if browser did reload and lost this state
;(window as ExtendedWindow).didReload = false
Expand Down
10 changes: 5 additions & 5 deletions tests/e2e/on-demand-app.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,9 @@ test.describe('app router on-demand revalidation', () => {
: 's-maxage=31536000, stale-while-revalidate=31536000, durable',
)

const date1 = await page.textContent('[data-testid="date-now"]')
const date1 = await page.getByTestId('date-now').textContent()

const h1 = await page.textContent('h1')
const h1 = await page.locator('h1').textContent()
expect(h1).toBe(expectedH1Content)

const response2 = await pollUntilHeadersMatch(new URL(pagePath, serverComponents.url).href, {
Expand Down Expand Up @@ -127,7 +127,7 @@ test.describe('app router on-demand revalidation', () => {
)

// the page is cached
const date2 = await page.textContent('[data-testid="date-now"]')
const date2 = await page.getByTestId('date-now').textContent()
expect(date2).toBe(date1)

const revalidate = await page.goto(new URL(revalidateApiPath, serverComponents.url).href)
Expand Down Expand Up @@ -159,7 +159,7 @@ test.describe('app router on-demand revalidation', () => {
)

// the page has now an updated date
const date3 = await page.textContent('[data-testid="date-now"]')
const date3 = await page.getByTestId('date-now').textContent()
expect(date3).not.toBe(date2)

const response4 = await pollUntilHeadersMatch(new URL(pagePath, serverComponents.url).href, {
Expand Down Expand Up @@ -188,7 +188,7 @@ test.describe('app router on-demand revalidation', () => {
)

// the page is cached
const date4 = await page.textContent('[data-testid="date-now"]')
const date4 = await page.getByTestId('date-now').textContent()
expect(date4).toBe(date3)
})
}
Expand Down
62 changes: 31 additions & 31 deletions tests/e2e/page-router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,12 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => {
)

if (fallbackWasServed) {
const loading = await page.textContent('[data-testid="loading"]')
const loading = await page.getByTestId('loading').textContent()
expect(loading, 'Fallback should be shown').toBe('Loading...')
}

const date1 = await page.textContent('[data-testid="date-now"]')
const h1 = await page.textContent('h1')
const date1 = await page.getByTestId('date-now').textContent()
const h1 = await page.locator('h1').textContent()
expect(h1).toBe(expectedH1Content)

// check json route
Expand Down Expand Up @@ -238,7 +238,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => {
)

// the page is cached
const date2 = await page.textContent('[data-testid="date-now"]')
const date2 = await page.getByTestId('date-now').textContent()
expect(date2).toBe(date1)

// check json route
Expand Down Expand Up @@ -299,7 +299,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => {
expect(headers3?.['x-nextjs-cache']).toBeUndefined()

// the page has now an updated date
const date3 = await page.textContent('[data-testid="date-now"]')
const date3 = await page.getByTestId('date-now').textContent()
expect(date3).not.toBe(date2)

// check json route
Expand Down Expand Up @@ -366,7 +366,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => {
},
)
expect(response1?.status()).toBe(200)
const date1 = (await page.textContent('[data-testid="date-now"]')) ?? ''
const date1 = (await page.getByTestId('date-now').textContent()) ?? ''

// ensure response was produced before invocation (served from cache)
expect(date1.localeCompare(beforeFetch)).toBeLessThan(0)
Expand All @@ -391,7 +391,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => {
},
)
expect(response2?.status()).toBe(200)
const date2 = (await page.textContent('[data-testid="date-now"]')) ?? ''
const date2 = (await page.getByTestId('date-now').textContent()) ?? ''

// ensure response was produced after initial invocation
expect(beforeFetch.localeCompare(date2)).toBeLessThan(0)
Expand All @@ -416,7 +416,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => {
)

// ensure response was NOT produced before invocation
const date1 = (await page.textContent('[data-testid="date-now"]')) ?? ''
const date1 = (await page.getByTestId('date-now').textContent()) ?? ''
expect(date1.localeCompare(beforeFirstFetch)).toBeGreaterThan(0)

// allow page to get stale
Expand All @@ -431,7 +431,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => {
/s-maxage=60, stale-while-revalidate=[0-9]+, durable/,
)

const date2 = (await page.textContent('[data-testid="date-now"]')) ?? ''
const date2 = (await page.getByTestId('date-now').textContent()) ?? ''
expect(date2).toBe(date1)

// wait a bit to ensure background work has a chance to finish
Expand All @@ -450,7 +450,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => {
/s-maxage=60, stale-while-revalidate=[0-9]+, durable/,
)

const date3 = (await page.textContent('[data-testid="date-now"]')) ?? ''
const date3 = (await page.getByTestId('date-now').textContent()) ?? ''
expect(date3.localeCompare(date2)).toBeGreaterThan(0)
})

Expand All @@ -469,7 +469,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => {
const headers = response?.headers() || {}
expect(response?.status()).toBe(404)

expect(await page.textContent('p')).toBe('Custom 404 page')
await expect(page.getByTestId('custom-404')).toHaveText('Custom 404 page')

// https://github.com/vercel/next.js/pull/69802 made changes to returned cache-control header,
// after that (14.2.10 and canary.147) 404 pages would have `private` directive, before that
Expand All @@ -493,7 +493,7 @@ test.describe('Simple Page Router (no basePath, no i18n)', () => {
const headers = response?.headers() || {}
expect(response?.status()).toBe(404)

expect(await page.textContent('p')).toBe('Custom 404 page')
await expect(page.getByTestId('custom-404')).toHaveText('Custom 404 page')

expect(headers['debug-netlify-cdn-cache-control']).toBe(
nextVersionSatisfies('>=15.0.0-canary.187')
Expand Down Expand Up @@ -748,12 +748,12 @@ test.describe('Page Router with basePath and i18n', () => {
)

if (fallbackWasServedImplicitLocale) {
const loading = await page.textContent('[data-testid="loading"]')
const loading = await page.getByTestId('loading').textContent()
expect(loading, 'Fallback should be shown').toBe('Loading...')
}

const date1ImplicitLocale = await page.textContent('[data-testid="date-now"]')
const h1ImplicitLocale = await page.textContent('h1')
const date1ImplicitLocale = await page.getByTestId('date-now').textContent()
const h1ImplicitLocale = await page.locator('h1').textContent()
expect(h1ImplicitLocale).toBe(expectedH1Content)

const response1ExplicitLocale = await pollUntilHeadersMatch(
Expand Down Expand Up @@ -790,12 +790,12 @@ test.describe('Page Router with basePath and i18n', () => {
)

if (fallbackWasServedExplicitLocale) {
const loading = await page.textContent('[data-testid="loading"]')
const loading = await page.getByTestId('loading').textContent()
expect(loading, 'Fallback should be shown').toBe('Loading...')
}

const date1ExplicitLocale = await page.textContent('[data-testid="date-now"]')
const h1ExplicitLocale = await page.textContent('h1')
const date1ExplicitLocale = await page.getByTestId('date-now').textContent()
const h1ExplicitLocale = await page.locator('h1').textContent()
expect(h1ExplicitLocale).toBe(expectedH1Content)

// implicit and explicit locale paths should be the same (same cached response)
Expand Down Expand Up @@ -861,7 +861,7 @@ test.describe('Page Router with basePath and i18n', () => {
)

// the page is cached
const date2ImplicitLocale = await page.textContent('[data-testid="date-now"]')
const date2ImplicitLocale = await page.getByTestId('date-now').textContent()
expect(date2ImplicitLocale).toBe(date1ImplicitLocale)

const response2ExplicitLocale = await pollUntilHeadersMatch(
Expand Down Expand Up @@ -893,7 +893,7 @@ test.describe('Page Router with basePath and i18n', () => {
)

// the page is cached
const date2ExplicitLocale = await page.textContent('[data-testid="date-now"]')
const date2ExplicitLocale = await page.getByTestId('date-now').textContent()
expect(date2ExplicitLocale).toBe(date1ExplicitLocale)

// check json route
Expand Down Expand Up @@ -961,7 +961,7 @@ test.describe('Page Router with basePath and i18n', () => {
expect(headers3ImplicitLocale?.['x-nextjs-cache']).toBeUndefined()

// the page has now an updated date
const date3ImplicitLocale = await page.textContent('[data-testid="date-now"]')
const date3ImplicitLocale = await page.getByTestId('date-now').textContent()
expect(date3ImplicitLocale).not.toBe(date2ImplicitLocale)

const response3ExplicitLocale = await pollUntilHeadersMatch(
Expand All @@ -984,7 +984,7 @@ test.describe('Page Router with basePath and i18n', () => {
expect(headers3ExplicitLocale?.['x-nextjs-cache']).toBeUndefined()

// the page has now an updated date
const date3ExplicitLocale = await page.textContent('[data-testid="date-now"]')
const date3ExplicitLocale = await page.getByTestId('date-now').textContent()
expect(date3ExplicitLocale).not.toBe(date2ExplicitLocale)

// implicit and explicit locale paths should be the same (same cached response)
Expand Down Expand Up @@ -1057,7 +1057,7 @@ test.describe('Page Router with basePath and i18n', () => {
expect(headers4ImplicitLocale?.['x-nextjs-cache']).toBeUndefined()

// the page has now an updated date
const date4ImplicitLocale = await page.textContent('[data-testid="date-now"]')
const date4ImplicitLocale = await page.getByTestId('date-now').textContent()
expect(date4ImplicitLocale).not.toBe(date3ImplicitLocale)

const response4ExplicitLocale = await pollUntilHeadersMatch(
Expand All @@ -1080,7 +1080,7 @@ test.describe('Page Router with basePath and i18n', () => {
expect(headers4ExplicitLocale?.['x-nextjs-cache']).toBeUndefined()

// the page has now an updated date
const date4ExplicitLocale = await page.textContent('[data-testid="date-now"]')
const date4ExplicitLocale = await page.getByTestId('date-now').textContent()
expect(date4ExplicitLocale).not.toBe(date3ExplicitLocale)

// implicit and explicit locale paths should be the same (same cached response)
Expand Down Expand Up @@ -1173,12 +1173,12 @@ test.describe('Page Router with basePath and i18n', () => {
)

if (fallbackWasServed) {
const loading = await page.textContent('[data-testid="loading"]')
const loading = await page.getByTestId('loading').textContent()
expect(loading, 'Fallback should be shown').toBe('Loading...')
}

const date1 = await page.textContent('[data-testid="date-now"]')
const h1 = await page.textContent('h1')
const date1 = await page.getByTestId('date-now').textContent()
const h1 = await page.locator('h1').textContent()
expect(h1).toBe(expectedH1Content)

// check json route
Expand Down Expand Up @@ -1241,7 +1241,7 @@ test.describe('Page Router with basePath and i18n', () => {
)

// the page is cached
const date2 = await page.textContent('[data-testid="date-now"]')
const date2 = await page.getByTestId('date-now').textContent()
expect(date2).toBe(date1)

// check json route
Expand Down Expand Up @@ -1309,7 +1309,7 @@ test.describe('Page Router with basePath and i18n', () => {
expect(headers3?.['x-nextjs-cache']).toBeUndefined()

// the page has now an updated date
const date3 = await page.textContent('[data-testid="date-now"]')
const date3 = await page.getByTestId('date-now').textContent()
expect(date3).not.toBe(date2)

// check json route
Expand Down Expand Up @@ -1360,7 +1360,7 @@ test.describe('Page Router with basePath and i18n', () => {
const headers = response?.headers() || {}
expect(response?.status()).toBe(404)

expect(await page.textContent('p')).toBe('Custom 404 page for locale: en')
await expect(page.getByTestId('custom-404')).toHaveText('Custom 404 page for locale: en')

expect(headers['debug-netlify-cdn-cache-control']).toMatch(
/no-cache, no-store, max-age=0, must-revalidate, durable/m,
Expand All @@ -1378,7 +1378,7 @@ test.describe('Page Router with basePath and i18n', () => {
const headers = response?.headers() || {}
expect(response?.status()).toBe(404)

expect(await page.textContent('p')).toBe('Custom 404 page for locale: en')
await expect(page.getByTestId('custom-404')).toHaveText('Custom 404 page for locale: en')

// Prior to v14.2.4 notFound pages are not cacheable
// https://github.com/vercel/next.js/pull/66674
Expand Down
8 changes: 5 additions & 3 deletions tests/e2e/simple-app.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect, type Locator, type Response } from '@playwright/test'
import { nextVersionSatisfies } from '../utils/next-version-helpers.mjs'
import { hasDefaultTurbopackBuilds, nextVersionSatisfies } from '../utils/next-version-helpers.mjs'
import { test } from '../utils/playwright-helpers.js'

const expectImageWasLoaded = async (locator: Locator) => {
Expand Down Expand Up @@ -227,7 +227,7 @@ test('requesting a non existing page route that needs to be fetched from the blo
const headers = response?.headers() || {}
expect(response?.status()).toBe(404)

expect(await page.textContent('h1')).toBe('404 Not Found')
await expect(page.locator('h1')).toHaveText('404 Not Found')

// https://github.com/vercel/next.js/pull/66674 made changes to returned cache-control header,
// before that 404 page would have `private` directive, after that (14.2.4 and canary.24) it
Expand All @@ -254,7 +254,7 @@ test('requesting a non existing page route that needs to be fetched from the blo
const headers = response?.headers() || {}
expect(response?.status()).toBe(404)

expect(await page.textContent('h1')).toBe('404 Not Found')
await expect(page.locator('h1')).toHaveText('404 Not Found')

expect(headers['debug-netlify-cdn-cache-control']).toBe(
nextVersionSatisfies('>=15.0.0-canary.187')
Expand All @@ -273,6 +273,8 @@ test('Compressed rewrites are readable', async ({ simple }) => {
})

test('can require CJS module that is not bundled', async ({ simple }) => {
// setup for this test only works with webpack builds due to usage of ` __non_webpack_require__` to avoid bundling a file
test.skip(hasDefaultTurbopackBuilds(), 'Setup for this test only works with webpack builds')
const resp = await fetch(`${simple.url}/api/cjs-file-with-js-extension`)

expect(resp.status).toBe(200)
Expand Down
Loading
Loading