Commit Graph

18 Commits

Author SHA1 Message Date
Jamiboy Mohammad 3dbf921905 test: enable verified runtime deploy tests (#98457)
## Summary

Enable the same 9 previously selected deployment-test scopes across 9
runtime test files, now in a stack rooted on canary. Remove 8
`skipDeployment` options and their obsolete skip guards. Enable only the
selected middleware cookie assertion; the neighboring query assertion
remains excluded. Other mode, bundler, middleware, and Cache Components
exclusions remain in place.

This preserves the selection with passing evidence from the previous
deployment runs. No additional candidate scopes are enabled; excluded
variants are not counted as deployment coverage.

The worker-react-refresh scope (ID 411) remains deployment-excluded
pending fixture dependency changes: React 19.3.0 conflicts with
@react-three/fiber 9.7.0’s React <19.3 peer range. Both deploy variants
fail during npm installation, before the assertion executes. It is
tracked as a fixable candidate, not a permanent local-only test.

## Verification

- All selected test registration names and assertion bodies match the
previous enabled revision, checked by AST comparison.
- Verified that the canary diff contains only the inventoried exclusions
and their obsolete skip plumbing; other exclusions are preserved.
- Formatting and lint passed; 77 gate infrastructure unit tests passed.
- Full local bootstrap was blocked by missing package-level dependencies
in the temporary worktree. Fresh deployment execution on these rewritten
commits remains to be verified in CI.

<details>
<summary>Preserved scope inventory (9)</summary>

- ID 350: `test/e2e/app-dir/app-edge-root-layout/index.test.ts` —
`describe('app-dir edge runtime root layout', () => {
  const { next, isNextStart } = nextTestSetup({
    files: __dirname,
  })

  it('should not emit metadata files into bad paths', async () => {
    await next.fetch('/favicon.ico')
// issue: If metadata files are not filter out properly with
image-loader,
    // an incorrect static/media folder will be generated

    // Check that the static folder is not generated
    const incorrectGeneratedStaticFolder = await next.hasFile('static')
    expect(incorrectGeneratedStaticFolder).toBe(false)
  })

  if (isNextStart) {
it('should mark static contain metadata routes as edge functions', async
() => {
      const middlewareManifest = await next.readFile(
        '.next/server/middleware-manifest.json'
      )
      expect(middlewareManifest).not.toContain('/favicon')
    })
  }
})`
- ID 353: `test/e2e/app-dir/binary/rsc-binary.test.ts` — `describe('RSC
binary serialization', () => {
  const { next } = nextTestSetup({
    files: __dirname,
    dependencies: {
      'server-only': 'latest',
    },
  })

  afterEach(async () => {
    await next.stop()
  })

it('should correctly encode/decode binaries and hydrate', async function
() {
    const browser = await next.browser('/')
    await check(async () => {
      const content = await browser.elementByCss('body').text()

      return content.includes('utf8 binary: hello') &&
        content.includes('arbitrary binary: 255,0,1,2,3') &&
        content.includes('hydrated: true')
        ? 'success'
        : 'fail'
    }, 'success')
  })
})`
- ID 355:
`test/e2e/app-dir/interception-middleware-rewrite/interception-middleware-rewrite.test.ts`
— `describe('interception-middleware-rewrite', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should support intercepting routes with a middleware rewrite', async
() => {
    const browser = await next.browser('/')

    await check(() => browser.elementByCss('#children').text(), 'root')

    await check(
      () =>
        browser
          .elementByCss('[href="/feed"]')
          .click()
          .elementByCss('#modal')
          .text(),
      'intercepted'
    )

    await check(
      () => browser.refresh().elementByCss('#children').text(),
      'not intercepted'
    )

    await check(() => browser.elementByCss('#modal').text(), 'default')
  })

it('should continue to work after using browser back button and
following another intercepting route', async () => {
    const browser = await next.browser('/')
    await check(() => browser.elementById('children').text(), 'root')

    await browser.elementByCss('[href="/photos/1"]').click()
    await check(
      () => browser.elementById('modal').text(),
      'Intercepted Photo ID: 1'
    )
    await browser.back()
    await browser.elementByCss('[href="/photos/2"]').click()
    await check(
      () => browser.elementById('modal').text(),
      'Intercepted Photo ID: 2'
    )
  })

it('should continue to show the intercepted page when revisiting it',
async () => {
    const browser = await next.browser('/')
    await check(() => browser.elementById('children').text(), 'root')

    await browser.elementByCss('[href="/photos/1"]').click()

    // we should be showing the modal and not the page
    await check(
      () => browser.elementById('modal').text(),
      'Intercepted Photo ID: 1'
    )

    await browser.refresh()

    // page should show after reloading the browser
    await check(
      () => browser.elementById('children').text(),
      'Page Photo ID: 1'
    )

    // modal should no longer be showing
    await check(() => browser.elementById('modal').text(), 'default')

    await browser.back()

    // revisit the same page that was intercepted
    await browser.elementByCss('[href="/photos/1"]').click()

    // ensure that we're still showing the modal and not the page
    await check(
      () => browser.elementById('modal').text(),
      'Intercepted Photo ID: 1'
    )

    // page content should not have changed
    await check(() => browser.elementById('children').text(), 'root')
  })
})`
- ID 360: `test/e2e/app-dir/middleware-matching/index.test.ts` —
`describe('app dir - middleware with custom matcher', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  it('should match /:id (without asterisk)', async () => {
    const browser = await next.browser('/chat/123')
    expect(await browser.elementByCss('p').text()).toBe('Home')
  })
})`
- ID 361:
`test/e2e/app-dir/node-extensions/node-extensions.random.test.ts` —
`describe('Cache Components', () => {
      const { next } = nextTestSetup({
        files: __dirname + '/fixtures/random/cache-components',
      })

it('should not error when accessing middlware that use Math.random()',
async () => {
        let res: Awaited<ReturnType<typeof next.fetch>>,
          $: Awaited<ReturnType<typeof next.render$>>

        res = await next.fetch('/rewrite')
        expect(res.status).toBe(200)
        $ = await next.render$('/rewrite')
        expect($('[data-testid="content"]').text()).toBe('rewritten')
      })

it('should not error when accessing pages that use Math.random() in App
Router', async () => {
        let res, $

        res = await next.fetch('/app/prerendered/unstable-cache')
        expect(res.status).toBe(200)
        $ = await next.render$('/app/prerendered/unstable-cache')
        expect($('li').length).toBe(2)

        res = await next.fetch('/app/prerendered/use-cache')
        expect(res.status).toBe(200)
        $ = await next.render$('/app/prerendered/use-cache')
        expect($('li').length).toBe(2)

        res = await next.fetch('/app/rendered/uncached')
        expect(res.status).toBe(200)
        $ = await next.render$('/app/rendered/uncached')
        expect($('li').length).toBe(2)

        res = await next.fetch('/app/rendered/unstable-cache')
        expect(res.status).toBe(200)
        $ = await next.render$('/app/rendered/unstable-cache')
        expect($('li').length).toBe(2)

        res = await next.fetch('/app/rendered/use-cache')
        expect(res.status).toBe(200)
        $ = await next.render$('/app/rendered/use-cache')
        expect($('li').length).toBe(2)
      })

it('should not error when accessing routes that use Math.random() in App
Router', async () => {
        let res, body

        res = await next.fetch('/app/prerendered/uncached/api')
        expect(res.status).toBe(200)
        body = await res.json()
        expect(body).toEqual({
          rand1: expect.any(Number),
          rand2: expect.any(Number),
        })

        res = await next.fetch('/app/prerendered/unstable-cache/api')
        expect(res.status).toBe(200)
        body = await res.json()
        expect(body).toEqual({
          rand1: expect.any(Number),
          rand2: expect.any(Number),
        })

        res = await next.fetch('/app/prerendered/use-cache/api')
        expect(res.status).toBe(200)
        body = await res.json()
        expect(body).toEqual({
          rand1: expect.any(Number),
          rand2: expect.any(Number),
        })

        res = await next.fetch('/app/rendered/uncached/api')
        expect(res.status).toBe(200)
        body = await res.json()
        expect(body).toEqual({
          rand1: expect.any(Number),
          rand2: expect.any(Number),
        })

        res = await next.fetch('/app/rendered/unstable-cache/api')
        expect(res.status).toBe(200)
        body = await res.json()
        expect(body).toEqual({
          rand1: expect.any(Number),
          rand2: expect.any(Number),
        })

        res = await next.fetch('/app/rendered/use-cache/api')
        expect(res.status).toBe(200)
        body = await res.json()
        expect(body).toEqual({
          rand1: expect.any(Number),
          rand2: expect.any(Number),
        })
      })

it('should not error when accessing pages that use Math.random() in
Pages Router', async () => {
        let res, $

        res = await next.fetch('/pages/gip/random')
        expect(res.status).toBe(200)
        $ = await next.render$('/pages/gip/random')
        expect($('li').length).toBe(2)

        res = await next.fetch('/pages/gssp/random')
        expect(res.status).toBe(200)
        $ = await next.render$('/pages/gssp/random')
        expect($('li').length).toBe(2)

        res = await next.fetch('/pages/gsp/random')
        expect(res.status).toBe(200)
        $ = await next.render$('/pages/gsp/random')
        expect($('li').length).toBe(2)
      })

it('should not error when accessing routes that use Math.random() in
Pages Router', async () => {
        let res, body

        res = await next.fetch('/api/random')
        expect(res.status).toBe(200)
        body = await res.json()
        expect(body).toEqual({
          rand1: expect.any(Number),
          rand2: expect.any(Number),
        })

        expect(body.rand1).not.toBe(body.rand2)

        const first1 = body.rand1
        const first2 = body.rand2

        res = await next.fetch('/api/random')
        body = await res.json()
        expect(body.rand1).not.toBe(body.rand2)
        expect(body.rand1).not.toBe(first1)
        expect(body.rand2).not.toBe(first2)
      })
    })`
- ID 370:
`test/e2e/app-dir/webpack-loader-binary/webpack-loader-binary.test.ts` —
`describe('webpack-loader-ts-transform', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

it('should allow passing binary assets to and from a Webpack loader',
async () => {
    const $ = await next.render$('/')
    expect($('#text').text()).toBe('Got a buffer of 18 bytes')
    expect($('#binary').text()).toBe('Got a buffer of 6765 bytes')
  })
})`
- ID 386:
`test/e2e/edge-runtime-uses-edge-light-import-specifier-for-packages/edge-runtime-uses-edge-light-import-specifier-for-packages.test.ts`
— `describe('edge-runtime uses edge-light import specifier for
packages', () => {
  const { next } = nextTestSetup({
    files: __dirname,
    packageJson: {
      scripts: {
        build: 'next build',
        dev: 'next dev',
        start: 'next start',
      },
    },
    installCommand: 'pnpm i',
    startCommand: (global as any).isNextDev ? 'pnpm dev' : 'pnpm start',
    buildCommand: 'pnpm build',
  })

  // In case you need to test the response object
  it('pages/api endpoints import the correct module', async () => {
    const res = await next.fetch('/api/edge')
    const html = await res.json()
    expect(html).toEqual({
// edge-light is only supported in `exports` and `imports` but webpack
also adds the top level `edge-light` key incorrectly.
edgeLightPackage: process.env.IS_TURBOPACK_TEST ? 'import' :
'edge-light',
      edgeLightPackageExports: 'edge-light',
    })
  })

  it('pages import the correct module', async () => {
    const $ = await next.render$('/')
    const text = JSON.parse($('pre#result').text())
    expect(text).toEqual({
// edge-light is only supported in `exports` and `imports` but webpack
also adds the top level `edge-light` key incorrectly.
edgeLightPackage: process.env.IS_TURBOPACK_TEST ? 'import' :
'edge-light',
      edgeLightPackageExports: 'edge-light',
    })
  })

  it('app-dir imports the correct module', async () => {
    const $ = await next.render$('/app-dir')
    const text = JSON.parse($('pre#result').text())
    expect(text).toEqual({
// edge-light is only supported in `exports` and `imports` but webpack
also adds the top level `edge-light` key incorrectly.
edgeLightPackage: process.env.IS_TURBOPACK_TEST ? 'import' :
'edge-light',
      edgeLightPackageExports: 'edge-light',
    })
  })
})`
- ID 392: `test/e2e/middleware-custom-matchers/test/index.test.ts` —
`it('should match has cookie on client routing', async () => {
      const browser = await next.browser('/routes')
      await browser.addCookie({ name: 'loggedIn', value: 'true' })
      await browser.refresh()
      await browser.eval('window.__TEST_NO_RELOAD = true')
      await browser.elementById('has-match-3').click()
const fromMiddleware = await
browser.elementById('from-middleware').text()
      expect(fromMiddleware).toBe('true')
      const noReload = await browser.eval('window.__TEST_NO_RELOAD')
      expect(noReload).toBe(true)
    })`
- ID 402:
`test/e2e/on-request-error/skip-next-internal-error/skip-next-internal-error.test.ts`
— `describe('on-request-error - skip-next-internal-error', () => {
  const { next } = nextTestSetup({
    files: __dirname,
  })

  async function assertNoNextjsInternalErrors() {
    const output = next.cliOutput
    // No navigation errors
    expect(output).not.toContain('NEXT_REDIRECT')
    expect(output).not.toContain('NEXT_NOT_FOUND')
    expect(output).not.toContain('BAILOUT_TO_CLIENT_SIDE_RENDERING')
    // No dynamic usage errors
    expect(output).not.toContain('DYNAMIC_SERVER_USAGE')
    // No react postpone errors
    // TODO: cover PPR errors later
    expect(output).not.toContain('react.postpone')
  }

  describe('app router render', () => {
    // Server navigation errors
it('should not catch server component not-found errors', async () => {
      await next.fetch('/server/not-found')
      await assertNoNextjsInternalErrors()
    })

it('should not catch server component redirect errors', async () => {
      await next.render('/server/redirect')
      await assertNoNextjsInternalErrors()
    })

    // Client navigation errors
it('should not catch client component not-found errors', async () => {
      await next.fetch('/server/not-found')
      await assertNoNextjsInternalErrors()
    })

it('should not catch client component redirect errors', async () => {
      await next.render('/client/redirect')
      await assertNoNextjsInternalErrors()
    })

    // Dynamic usage
it('should not catch server component dynamic usage errors', async () =>
{
      await next.fetch('/server/dynamic-fetch')
      await assertNoNextjsInternalErrors()
    })

it('should not catch client component dynamic usage errors', async () =>
{
      await next.fetch('/client/dynamic-fetch')
      await assertNoNextjsInternalErrors()
    })

    // No SSR
    it('should not catch next dynamic no-ssr errors', async () => {
      await next.fetch('/client/no-ssr')
      await assertNoNextjsInternalErrors()
    })

    // Server Actions navigation
    it('should not catch server action not-found errors', async () => {
      await next.fetch('/form/not-found')
      await assertNoNextjsInternalErrors()
    })

    it('should not catch server action redirect errors', async () => {
      await next.fetch('/form/redirect')
      await assertNoNextjsInternalErrors()
    })
  })

  describe('app router API', () => {
    // API routes navigation errors
it('should not catch server component not-found errors', async () => {
      await next.render('/app-route/not-found')
      await assertNoNextjsInternalErrors()
    })

it('should not catch server component redirect errors', async () => {
      await next.render('/app-route/redirect')
      await assertNoNextjsInternalErrors()
    })
  })
})`


</details>

<details>
<summary>Deployment evidence for the additional scopes</summary>

- `test/e2e/middleware-custom-matchers/test/index.test.ts` — `it('should
match has cookie on client routing', async () => {`:
[normal](https://github.com/vercel/next.js/actions/runs/34426785013/job/102715655249),
[cache](https://github.com/vercel/next.js/actions/runs/34426785013/job/102715655202).

</details>

<!-- NEXT_JS_LLM -->
2026-09-15 10:29:07 -07:00
Lazizbek Ergashev 0c7f81e6a3 Don't report a client-aborted RSC stream as a render error (#96715)
When a client disconnects mid-stream, `pipeNodeReadableToNodeResponse`
destroys the readable React is piping into. React sees its destination
close before the render ended and aborts with a generic `Error("The
destination stream closed early.")`, which `onError` has no way to
distinguish from a real render error, so it ends up in `onRequestError`
as a server render error.

The Web streams path doesn't have this problem: cancelling the readable
passes the `ResponseAborted` reason straight through to React. This does
the same for the Node path by aborting the pipeable with that reason as
soon as the passthrough is destroyed early, before React's own close
handler runs. Genuine render errors still go through untouched.

Fixes #96704

---------

Co-authored-by: Jude Gao <jude.gao@vercel.com>
2026-08-26 16:26:01 -04:00
JJ Kasper e598a4f976 Add new updateTag API and new signature for revalidateTag (#83822) 2025-10-09 14:03:01 -07:00
yiminghe 458605e68d fix: ensure onRequestError is invoked when otel enabled (#83343)
Co-authored-by: Jiachi Liu <inbox@huozhi.im>
2025-09-03 16:33:35 +00:00
Jiachi Liu 93e1a4c71e tests: migrate on-reuqest-error tests to new model (#83383) 2025-09-03 16:47:47 +02:00
JJ Kasper 1dcc7fe97a Implement handler interface for app-page (#79568)
Continuation of https://github.com/vercel/next.js/pull/78166 this
implements the handler interface for app router pages, This does not
move the response handling inside of the handler yet as that will be in
a follow-up PR to keep the changes isolated. This still returns the
RenderResult for the base-server to continue to handle.

Validated against deploy tests
https://github.com/vercel/vercel/pull/13389 and
https://github.com/vercel/next.js/actions/runs/15358616431/job/43222466204
2025-05-31 11:04:43 +09:00
JJ Kasper a771ce2ff2 Undeprecate revalidate APIs and rename expire APIs (#73193)
As discussed we are removing the deprecated field from `revalidateTag`
and `revalidatePath` as they aren't going away anytime soon and the new
`expire` APIs will have slightly different semantics so not a 1-1 rename
with no arguments change anymore. We are also adding the `unstable_`
prefix for `expireTag`/`expirePath` while we iterate on them so it's
clear they will change.

---------

Co-authored-by: Zack Tanner <1939140+ztanner@users.noreply.github.com>
2024-11-27 09:01:04 -08:00
JJ Kasper 0bc4086dff Add expireTag and expirePath APIs (#72485)
This adds `expireTag()` and `expirePath` APIs which are replacing
`revalidateTag` and `revalidatePath`. As such this also marks
`revalidatePath` and `revalidateTag` as deprecated and marked for
removal.

The arguments passed to these methods match `revalidateTag` and
`revalidatePath` with the exception that `expireTag` can receive
multiple tags now as separate arguments which matches our new
`unstable_cacheTag()` API.

We are wanting to update these APIs as `revalidateTag` naming has caused
some confusion as to when the affected items will be updated. The
underlying behavior of `expireTag` is to mark an entry as out of date
and for it to no longer be used so we are expiring it.

If you call `expirePath('/blog/first'`) the next time that item is
requested it is considered expired so the cache will not be used for it
and a fresh entry will be generated. Similarly for `expireTag('posts',
'blog')` the next time any cache with that tag is requested it's
respective cache will not be leveraged and new cache will be populated.

---------

Co-authored-by: Zack Tanner <1939140+ztanner@users.noreply.github.com>
Co-authored-by: Delba de Oliveira <32464864+delbaoliveira@users.noreply.github.com>
2024-11-11 19:23:39 +00:00
Hendrik Liebau 604b87cc28 Increase retry duration for HMR fetch logging tests (#71170)
As we've learned in #70467, compilation times can be quite high in CI,
when testing HMR behaviour. The fetch logging HMR tests are frequently
failing. Increasing the `retry` timeout should mitigate the flakiness.
2024-10-12 02:27:01 +02:00
Jiachi Liu c9daf1fc8c Stablize instrumentation.js (#68853)
### What

- After adding a new API `onRequestError()` and a few fixes on `register()` for `instrumentation.js`, it's time to promote it as stable API.
- Add warning `"experimental.instrumentationHook is no longer needed to be configured in Next.js"` when you configure a `instrumentationHook` option in next.config.js

Related PRs
#67539, #67671, #67703, #67848, #67859, #67856, #67805, #68616, #68672, #68764, #68845 #68976, #68983

Closes NDX-89
2024-08-21 15:55:26 +02:00
Jiachi Liu d714bb32aa feat: support isr reason for on request error (#68845)
### What

Provide `revalidateReason` in the `errorContext` argument of
`onRequestError` API

#### API

```ts
revalidateReason?: 'on-demand' | 'stale' | undefined
```

### Why

With this property, developer is able to determine if current error is
attached to an ISR request.

* `stale` means it revalidated due to the cache is expired
* `on-demand` means it revalidated due to a user initiated revalidation,
e.g. using `res.revalidate` to revalidate certain path

### Misc

* We don't generate edge entry of instrumentation when there's no edge
routes but instrumentation is presented

Note: for PPR case we'll tackle it later with another PPR specific task,
since we need to provide the render is a postpone render or just normal
render

Closes NDX-23
2024-08-14 21:44:51 +02:00
Jiachi Liu 11fa91b89f Rename url to path for onRequestError request arg (#68672)
### What

* Rename the `request.url` in onRequestError callback arg to
`request.path`
* Align `routeType: "middleware"` with others, only contain the resource
path in the `request.path`, stripped out request origin.

### Why

For non middleware case, the `req.url` in next-server is actual resource
path, which only contains the pathname and search but without request
origin. For middleware case, the origin is included in the `req.url`.
This might be a implementation detail, but for users, having request
origin here might not be critical and super helpful since they will know
the deployment as a global context for error tracking.

Hence we align the property to a variable that not holding request
origin, given a better naming `request.path`. The idea is from `:path`
request header, which represents the resource path, only containing
pathname and search. e.g. `/a/b?c=2`

x-ref:
https://greenbytes.de/tech/webdav/draft-ietf-httpbis-http2-09.html#HttpRequest
Closes NDX-54
2024-08-12 11:48:49 +02:00
Jiachi Liu e48eb5a2a9 Drop the experimental env var for onRequestError API (#67856)
### What

Since the API is getting more stablized with few past iteration and it's
still under the experimental flag, it's good to ship as a API along with
the `instrumentation.js`
2024-07-17 22:27:14 +02:00
Jiachi Liu b2e711aaea test: add dynamic routes and suspense test case for onRequestError (#67848)
### What

Add tests cases for dynamic routes and suspense page rendering case coverage for onRequestError.

Mostly for checking the `routePath` where we can display the proper original route path

Closes NDX-22
Closes NDX-79
2024-07-17 21:35:17 +02:00
Jiachi Liu f2adcfeb16 Handle server actions error in onRequestError (#67769)
### What

Handling server action errors capturing in instrumentation.js
`onRequestError` API

Closes NDX-21
2024-07-16 20:47:47 +02:00
Jiachi Liu 33e14f09b6 Add render source to onRequestError context (#67703)
### What

Provide `renderSource` for app router pages rendering errors in
instrumentation.js `onRequestError`.

Discussed with @gnoff that we refactored the error handlers a bit to
decouple from the Error render source since the handlers act differently
in different render phase.

### Why

This provides an easy way to determine wether the renderign error is
actually from SSR or RSC rendering. Since the RSC error is embedded in
the flight data and the final errors is only reported through React SSR
rendering.

Previously you can use the `digest` property sent to browser to
associate the actual error logged with your o11y provider, but it still
takes some effort, this would be the easy way to capture the original
source

Closes NDX-24
2024-07-12 19:28:37 +02:00
Jiachi Liu 38a6b018fd test: the nextjs internal errors should not be caught (#67671)
### What

Cover the Next.js internal errors are not captured by instrumentation
onRequestError API

Also skip deployment tests for onRequestError API due to the private env
flag `__NEXT_EXPERIMENTAL_INSTRUMENTATION` cannot be used in deployment

<!-- Closes NDX-33 -->
2024-07-12 18:38:31 +02:00
Jiachi Liu ba3959bb46 feat: instrumentation onRequestError (#67539)
## What

This PR introduces a new API `onRequestError` in `instrumentation.js`
convention, which can help you track the errors thrown from pages and
routes on server side.

### API

```ts
type RequestInfo = {
  url: string
  method: string
  headers: Record<string, string | string [] | undefined>
}

type ErrorContext = {
  routerKind: 'Pages Router' | 'App Router'
  routePath: string
  routeType: 'render' | 'route' | 'middleware'
}

export function onRequestError(error: unknown, request: RequestInfo, errorContext: ErrorContext) {
}
```

This experimental feature is now scoped behind an experimental env var
`__NEXT_EXPERIMENTAL_INSTRUMENTATION` now. You need to enable to use it
before the feature is fully ready off from experimental.

## Why

The purpose is to provide a way to track the server errors from Next.js
much easier, especially when users're uing an o11y provider such as
sentry/datadog/newrelic etc. to monitor server side exceptions. There're
different runtime (Node.js or Edge) and different type of routes (App
Router pages/API routes, Pages Router pages/API routes, middleware) that
makes the error tracking story more complex. This API will be an
universal way to get all the errors.

The reason of providing the related arguments like request info and
error context is aimed to provide more insights about associated
request, also the context about Next.js framework itself, like which
feature is throwing the error.
2024-07-10 19:05:30 +02:00