Skip to content
Draft
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
19 changes: 8 additions & 11 deletions packages/vinext/src/entries/app-rsc-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2036,28 +2036,25 @@ async function _handleRequest(request, __reqCtx, _mwCtx) {
setHeadersAccessPhase(previousHeadersPhase);
}

// If the action called redirect(), signal the client to navigate.
// We can't use a real HTTP redirect (the fetch would follow it automatically
// and receive a page HTML instead of RSC stream). Instead, we return a 200
// with x-action-redirect header that the client entry detects and handles.
// If the action called redirect(), return a 303 redirect response.
// This matches Next.js behavior for server action redirects.
// The client will handle the redirect and navigate to the target URL.
if (actionRedirect) {
const actionPendingCookies = getAndClearPendingCookies();
const actionDraftCookie = getDraftModeCookieHeader();

setHeadersContext(null);
setNavigationContext(null);
const redirectHeaders = new Headers({
"Content-Type": "text/x-component; charset=utf-8",
"Vary": "RSC, Accept",
"x-action-redirect": actionRedirect.url,
"x-action-redirect-type": actionRedirect.type,
"x-action-redirect-status": String(actionRedirect.status),
Location: actionRedirect.url,
});
for (const cookie of actionPendingCookies) {
redirectHeaders.append("Set-Cookie", cookie);
}
if (actionDraftCookie) redirectHeaders.append("Set-Cookie", actionDraftCookie);
// Send an empty RSC-like body (client will navigate instead of parsing)
return new Response("", { status: 200, headers: redirectHeaders });

// Return 303 See Other for server action redirects (Next.js behavior)
return new Response(null, { status: 303, headers: redirectHeaders });
}

// After the action, re-render the current page so the client
Expand Down
14 changes: 6 additions & 8 deletions packages/vinext/src/server/app-browser-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,27 +133,25 @@ function registerServerActionCallback(): void {

const actionRedirect = fetchResponse.headers.get("x-action-redirect");
if (actionRedirect) {
// Check for external URLs that need a hard redirect.
try {
const redirectUrl = new URL(actionRedirect, window.location.origin);
if (redirectUrl.origin !== window.location.origin) {
window.location.href = actionRedirect;
return undefined;
}
} catch {
// Fall through to client-side navigation if URL parsing fails.
// Fall through to hard redirect below if URL parsing fails.
}

// For same-origin redirects, use hard redirect to ensure navigation completes.
// This matches Next.js behavior for server action redirects with useActionState.
const redirectType = fetchResponse.headers.get("x-action-redirect-type") ?? "replace";
if (redirectType === "push") {
window.history.pushState(null, "", actionRedirect);
window.location.assign(actionRedirect);
} else {
window.history.replaceState(null, "", actionRedirect);
window.location.replace(actionRedirect);
}

if (typeof window.__VINEXT_RSC_NAVIGATE__ === "function") {
await window.__VINEXT_RSC_NAVIGATE__(actionRedirect);
}

return undefined;
}

Expand Down
Loading
Loading