dotnet/aspnetcore · error
Cannot perform enhanced form submission that changes the URL
Error message
Cannot perform enhanced form submission that changes the URL (except via a redirection), because then back/forward would not work. Either remove this form's 'action' attribute, or change its method to 'get', or do not mark it as enhanced.
Old URL: ${location.href}
New URL: ${response.url} What it means
Thrown by performEnhancedPageLoad (NavigationEnhancement.ts:362) after SSR completes, when a non-GET enhanced form post succeeded and changed the URL path without issuing a redirection. Browser back/forward relies on GET idempotency ('Resubmit form?' prompt); a POST that lands on a new URL without redirect cannot be re-requested, so history would break. Blazor defers the throw until after SSR completes in case the server emits a late redirect signal.
Source
Thrown at src/Components/Web.JS/src/Services/NavigationEnhancement.ts:362
// has since started). So finally, recreate the native "scroll to hash" behavior.
const hashPosition = internalDestinationHref.indexOf('#');
if (hashPosition >= 0) {
const hash = internalDestinationHref.substring(hashPosition + 1);
const targetElem = document.getElementById(hash);
targetElem?.scrollIntoView();
}
performingEnhancedPageLoad = false;
navigationEnhancementCallbacks.enhancedNavigationCompleted();
// For non-GET requests, the destination has to be the same URL you're already on, or result in a redirection
// (post/redirect/get). You're not allowed to POST to a different URL without redirecting, because then back/forwards
// won't work - we can't recreate the "Resubmit form?" behavior.
// See https://github.com/dotnet/aspnetcore/issues/50945
// The reason we delay throwing until after SSR completes is that SSR might include a redirection signal. If we get
// here without navigating away, it's an error.
if (isNonRedirectedPostToADifferentUrlMessage) {
throw new Error(isNonRedirectedPostToADifferentUrlMessage);
}
}
}
async function getResponsePartsWithFraming(responsePromise: Promise<Response>, abortSignal: AbortSignal, onInitialDocument: (response: Response, initialDocumentText: string) => void, onStreamingElement: (streamingElementMarkup) => void) {
let response: Response;
try {
response = await responsePromise;
if (!response.body) { // Not sure how this can happen, but the TypeScript annotations suggest it can
onInitialDocument(response, '');
return;
}
const frameBoundary = response.headers.get('ssr-framing');
if (!frameBoundary) {
// Shouldn't happen, but perhaps some proxy stripped the headers. In that case we just won't respect streaming and willView on GitHub (pinned to 294cab2f9b)
Solutions
- Follow Post/Redirect/Get: have the POST handler return a 30x redirect to a GET endpoint.
- Remove the form's 'action' attribute so it posts to the current URL.
- Change the form method to 'get'.
- Remove data-enhance from the form.
Example fix
<!-- before -->
<form method="post" data-enhance action="/save">
<!-- after: PRG pattern -->
<form method="post" data-enhance action="/save">
<!-- server: return Redirect("/saved") after processing --> Defensive patterns
Strategy: validation
Validate before calling
function followsPrgPattern(form: HTMLFormElement): boolean {
const method = (form.getAttribute('method') || 'get').toLowerCase();
if (method === 'get') return true;
// POST forms should not change the URL path unless they redirect
try {
const u = new URL(form.getAttribute('action') || form.action, document.baseURI);
return isForSamePath(u.href, location.href);
} catch { return false; }
} Type guard
function isUrlChangingPost(form: HTMLFormElement): boolean {
const method = (form.getAttribute('method') || 'get').toLowerCase();
if (method === 'get') return false;
try {
const u = new URL(form.getAttribute('action') || form.action, document.baseURI);
return !isForSamePath(u.href, location.href);
} catch { return true; }
} Try / catch
try {
await performEnhancedPageLoad(url, false, fetchOptions);
} catch (e) {
if (/changes the URL/.test((e as Error).message)) {
// instruct server to follow Post/Redirect/Get, or drop data-enhance
} else { throw e; }
} Prevention
- Follow Post/Redirect/Get for enhanced forms that change the URL.
- Omit the action attribute or use method='get' for enhanced forms.
- Remove data-enhance when you cannot guarantee a redirect.
When it happens
Trigger: An enhanced form (data-enhance) with method POST and an 'action' pointing to a different path than the current page, where the server returns 2xx without a 30x redirect and without the blazor-enhanced-nav-redirect-location header.
Common situations: Posting to /save and having the handler return content directly at /save (post/redirect/get pattern not followed); setting form action to a different URL than the page; mixing form posts with URL changes intentionally.
Related errors
- Enhanced navigation does not support making a non-GET reques
- Enhanced navigation does not support making a non-GET reques
- No enhanced programmatic navigation handler has been attache
- Cannot merge mismatching component descriptors: ${JSON.strin
- Renderer with ID '${rendererId}' is not listening for naviga
AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06).
Data as JSON: /api/errors/c4cfeda28086916a.
Report an issue: GitHub.