Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

actions: fix redirect on the edge #49197

Merged
merged 2 commits into from
May 4, 2023
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
3 changes: 0 additions & 3 deletions packages/next/src/server/app-render/action-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,9 +318,6 @@ export async function handleAction({
return actionResult
} catch (err) {
if (isRedirectError(err)) {
if (process.env.NEXT_RUNTIME === 'edge') {
throw new Error('Invariant: not implemented.')
}
const redirectUrl = getURLFromRedirectError(err)

// if it's a fetch action, we don't want to mess with the status code
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/server/web/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ export async function adapter(
* the incoming request was a data request.
*/
const redirect = response?.headers.get('Location')
if (response && redirect) {
if (response && redirect && !isEdgeRendering) {
const redirectURL = new NextURL(redirect, {
forceLocale: false,
headers: params.request.headers,
Expand Down
30 changes: 29 additions & 1 deletion test/e2e/app-dir/actions/app-action.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ createNextDescribe(
'app-dir action handling',
{
files: __dirname,
skipDeployment: true,
},
({ next, isNextDev }) => {
it('should handle basic actions correctly', async () => {
Expand Down Expand Up @@ -229,6 +228,35 @@ createNextDescribe(
return browser.elementByCss('h1').text()
}, 'Prefix: HELLO, WORLD')
})

it('should handle redirect to a relative URL in a single pass', async () => {
const browser = await next.browser('/client/edge')

await new Promise((resolve) => {
setTimeout(resolve, 3000)
})

let requests = []

browser.on('request', (req: Request) => {
requests.push(new URL(req.url()).pathname)
})

await browser.elementByCss('#redirect').click()

// no other requests should be made
expect(requests).toEqual(['/client/edge'])
})

it('should handle regular redirects', async () => {
const browser = await next.browser('/client/edge')

await browser.elementByCss('#redirect-external').click()

await check(async () => {
return browser.eval('window.location.toString()')
}, 'https://example.com/')
})
})

describe('fetch actions', () => {
Expand Down
59 changes: 59 additions & 0 deletions test/e2e/app-dir/actions/app/client/edge/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use client'

import { useState } from 'react'

import double, { inc, dec, redirectAction } from '../actions'

export default function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<h1>{count}</h1>
<button
id="inc"
onClick={async () => {
const newCount = await inc(count)
setCount(newCount)
}}
>
+1
</button>
<button
id="dec"
onClick={async () => {
const newCount = await dec(count)
setCount(newCount)
}}
>
-1
</button>
<button
id="double"
onClick={async () => {
const newCount = await double(count)
setCount(newCount)
}}
>
*2
</button>
<form>
<button
id="redirect"
formAction={() => redirectAction('/redirect-target')}
>
redirect
</button>
</form>
<form>
<button
id="redirect-external"
formAction={() => redirectAction('https://example.com')}
>
redirect external
</button>
</form>
</div>
)
}

export const runtime = 'edge'
8 changes: 7 additions & 1 deletion test/e2e/app-dir/actions/app/header/edge/page.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { getCookie, getHeader, setCookie } from '../actions'
import {
getCookie,
getHeader,
setCookie,
setCookieAndRedirect,
} from '../actions'
import UI from '../ui'
import { validator } from './validator'

Expand All @@ -9,6 +14,7 @@ export default function Page() {
getCookie={getCookie}
getHeader={getHeader}
setCookie={setCookie}
setCookieAndRedirect={setCookieAndRedirect}
getAuthedUppercase={validator(async (str) => {
'use server'
return prefix + ' ' + str.toUpperCase()
Expand Down