Skip to content

Commit

Permalink
Handle redirect in the same way as 404
Browse files Browse the repository at this point in the history
Expands on the approach from vercel#40787
  • Loading branch information
timneutkens committed Sep 22, 2022
1 parent c49e037 commit 5c5ae21
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 30 deletions.
86 changes: 72 additions & 14 deletions packages/next/client/components/layout-router.client.tsx
Expand Up @@ -11,7 +11,10 @@ import type {
ChildProp,
//Segment
} from '../../server/app-render'
import type { ChildSegmentMap } from '../../shared/lib/app-router-context'
import type {
AppRouterInstance,
ChildSegmentMap,
} from '../../shared/lib/app-router-context'
import type {
FlightRouterState,
FlightSegmentPath,
Expand All @@ -21,6 +24,7 @@ import {
LayoutRouterContext,
GlobalLayoutRouterContext,
TemplateContext,
AppRouterContext,
} from '../../shared/lib/app-router-context'
import { fetchServerResponse } from './app-router.client'
import { createInfinitePromise } from './infinite-promise'
Expand Down Expand Up @@ -285,6 +289,56 @@ function LoadingBoundary({
return <>{children}</>
}

interface RedirectBoundaryProps {
router: AppRouterInstance
children: React.ReactNode
}

function InfinitePromiseComponent() {
use(createInfinitePromise())
return <></>
}

class RedirectErrorBoundary extends React.Component<
RedirectBoundaryProps,
{ redirect: string | null }
> {
constructor(props: RedirectBoundaryProps) {
super(props)
this.state = { redirect: null }
}

static getDerivedStateFromError(error: any) {
if (error.code === 'NEXT_REDIRECT') {
return { redirect: error.url }
}
// Re-throw if error is not for 404
throw error
}

render() {
const redirect = this.state.redirect
if (redirect !== null) {
setTimeout(() => {
// @ts-ignore startTransition exists
React.startTransition(() => {
this.props.router.replace(redirect, {})
})
})
return <InfinitePromiseComponent />
}

return this.props.children
}
}

function RedirectBoundary({ children }: { children: React.ReactNode }) {
const router = useContext(AppRouterContext)
return (
<RedirectErrorBoundary router={router}>{children}</RedirectErrorBoundary>
)
}

interface NotFoundBoundaryProps {
notFound?: React.ReactNode
children: React.ReactNode
Expand Down Expand Up @@ -457,19 +511,23 @@ export default function OuterLayoutRouter({
<ErrorBoundary errorComponent={error}>
<LoadingBoundary loading={loading}>
<NotFoundBoundary notFound={notFound}>
<InnerLayoutRouter
parallelRouterKey={parallelRouterKey}
url={url}
tree={tree}
childNodes={childNodesForParallelRouter!}
childProp={
childPropSegment === preservedSegment ? childProp : null
}
segmentPath={segmentPath}
path={preservedSegment}
isActive={currentChildSegment === preservedSegment}
rootLayoutIncluded={rootLayoutIncluded}
/>
<RedirectBoundary>
<InnerLayoutRouter
parallelRouterKey={parallelRouterKey}
url={url}
tree={tree}
childNodes={childNodesForParallelRouter!}
childProp={
childPropSegment === preservedSegment
? childProp
: null
}
segmentPath={segmentPath}
path={preservedSegment}
isActive={currentChildSegment === preservedSegment}
rootLayoutIncluded={rootLayoutIncluded}
/>
</RedirectBoundary>
</NotFoundBoundary>
</LoadingBoundary>
</ErrorBoundary>
Expand Down
15 changes: 0 additions & 15 deletions packages/next/client/components/redirect.ts
@@ -1,21 +1,6 @@
import React, { experimental_use as use } from 'react'
import { AppRouterContext } from '../../shared/lib/app-router-context'
import { createInfinitePromise } from './infinite-promise'

export const REDIRECT_ERROR_CODE = 'NEXT_REDIRECT'

export function redirect(url: string) {
if (process.browser) {
const router = use(AppRouterContext)
setTimeout(() => {
// @ts-ignore startTransition exists
React.startTransition(() => {
router.replace(url, {})
})
})
// setTimeout is used to start a new transition during render, this is an intentional hack around React.
use(createInfinitePromise())
}
// eslint-disable-next-line no-throw-literal
throw {
url,
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/app-dir/index.test.ts
Expand Up @@ -1513,7 +1513,7 @@ describe('app dir', () => {
)
})

it('should redirect in a client component', async () => {
it.skip('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(
Expand Down

0 comments on commit 5c5ae21

Please sign in to comment.