diff --git a/packages/next/client/components/layout-router.tsx b/packages/next/client/components/layout-router.tsx
index f19e762eebf63..698b2e7ef8c42 100644
--- a/packages/next/client/components/layout-router.tsx
+++ b/packages/next/client/components/layout-router.tsx
@@ -278,9 +278,13 @@ interface RedirectBoundaryProps {
children: React.ReactNode
}
-function InfinitePromiseComponent() {
- use(createInfinitePromise())
- return <>>
+function HandleRedirect({ redirect }: { redirect: string }) {
+ const router = useContext(AppRouterContext)
+
+ useEffect(() => {
+ router.replace(redirect, {})
+ }, [redirect, router])
+ return null
}
class RedirectErrorBoundary extends React.Component<
@@ -297,20 +301,14 @@ class RedirectErrorBoundary extends React.Component<
const url = error.digest.split(';')[1]
return { redirect: url }
}
- // Re-throw if error is not for 404
+ // Re-throw if error is not for redirect
throw error
}
render() {
const redirect = this.state.redirect
if (redirect !== null) {
- setTimeout(() => {
- // @ts-ignore startTransition exists
- React.startTransition(() => {
- this.props.router.replace(redirect, {})
- })
- })
- return
+ return
}
return this.props.children
@@ -348,7 +346,12 @@ class NotFoundErrorBoundary extends React.Component<
render() {
if (this.state.notFoundTriggered) {
- return this.props.notFound
+ return (
+ <>
+
+ {this.props.notFound}
+ >
+ )
}
return this.props.children
diff --git a/packages/next/client/components/react-dev-overlay/hot-reloader.tsx b/packages/next/client/components/react-dev-overlay/hot-reloader.tsx
index b982fa1d50904..e7e90806662e8 100644
--- a/packages/next/client/components/react-dev-overlay/hot-reloader.tsx
+++ b/packages/next/client/components/react-dev-overlay/hot-reloader.tsx
@@ -445,6 +445,16 @@ export default function HotReload({
})
const handleOnUnhandledError = useCallback((ev) => {
+ if (
+ ev.error &&
+ ev.error.digest &&
+ (ev.error.digest.startsWith('NEXT_REDIRECT') ||
+ ev.error.digest === 'NEXT_NOT_FOUND')
+ ) {
+ ev.preventDefault()
+ return
+ }
+
hadRuntimeError = true
onUnhandledError(dispatch, ev)
}, [])
diff --git a/test/e2e/app-dir/app/app/not-found/clientcomponent/a.js b/test/e2e/app-dir/app/app/not-found/clientcomponent/page.js
similarity index 100%
rename from test/e2e/app-dir/app/app/not-found/clientcomponent/a.js
rename to test/e2e/app-dir/app/app/not-found/clientcomponent/page.js
diff --git a/test/e2e/app-dir/index.test.ts b/test/e2e/app-dir/index.test.ts
index 9d98f0400fec7..134dab910a10d 100644
--- a/test/e2e/app-dir/index.test.ts
+++ b/test/e2e/app-dir/index.test.ts
@@ -1693,32 +1693,44 @@ describe('app dir', () => {
expect(
await browser.waitForElementByCss('#not-found-component').text()
).toBe('Not Found!')
+ expect(
+ await browser
+ .waitForElementByCss('meta[name="robots"]')
+ .getAttribute('content')
+ ).toBe('noindex')
})
- it.skip('should trigger not-found in a client component', async () => {
+ it('should trigger not-found in a client component', async () => {
const browser = await webdriver(next.url, '/not-found/clientcomponent')
expect(
await browser.waitForElementByCss('#not-found-component').text()
).toBe('Not Found!')
+ expect(
+ await browser
+ .waitForElementByCss('meta[name="robots"]')
+ .getAttribute('content')
+ ).toBe('noindex')
})
- ;(isDev ? it.skip : it)(
- 'should trigger not-found client-side',
- async () => {
- const browser = await webdriver(next.url, '/not-found/client-side')
+ it('should trigger not-found client-side', async () => {
+ const browser = await webdriver(next.url, '/not-found/client-side')
+ await browser
+ .elementByCss('button')
+ .click()
+ .waitForElementByCss('#not-found-component')
+ expect(await browser.elementByCss('#not-found-component').text()).toBe(
+ 'Not Found!'
+ )
+ expect(
await browser
- .elementByCss('button')
- .click()
- .waitForElementByCss('#not-found-component')
- expect(
- await browser.elementByCss('#not-found-component').text()
- ).toBe('Not Found!')
- }
- )
+ .waitForElementByCss('meta[name="robots"]')
+ .getAttribute('content')
+ ).toBe('noindex')
+ })
})
describe('redirect', () => {
describe('components', () => {
- it.skip('should redirect in a server component', async () => {
+ it('should redirect in a server component', async () => {
const browser = await webdriver(next.url, '/redirect/servercomponent')
await browser.waitForElementByCss('#result-page')
expect(await browser.elementByCss('#result-page').text()).toBe(
@@ -1726,7 +1738,7 @@ describe('app dir', () => {
)
})
- it.skip('should redirect in a client component', async () => {
+ it('should redirect in a client component', async () => {
const browser = await webdriver(next.url, '/redirect/clientcomponent')
await browser.waitForElementByCss('#result-page')
expect(await browser.elementByCss('#result-page').text()).toBe(
@@ -1735,12 +1747,13 @@ describe('app dir', () => {
})
// TODO-APP: Enable in development
- ;(isDev ? it.skip : it)('should redirect client-side', async () => {
+ it('should redirect client-side', async () => {
const browser = await webdriver(next.url, '/redirect/client-side')
await browser
.elementByCss('button')
.click()
.waitForElementByCss('#result-page')
+ // eslint-disable-next-line jest/no-standalone-expect
expect(await browser.elementByCss('#result-page').text()).toBe(
'Result Page'
)