{"record":{"id":"c4cfeda28086916a","repo":"dotnet/aspnetcore","slug":"cannot-perform-enhanced-form-submission-that-chang","errorCode":null,"errorMessage":"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.\nOld URL: ${location.href}\nNew URL: ${response.url}","messagePattern":"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\\.\nOld URL: (.+?)\nNew URL: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/Components/Web.JS/src/Services/NavigationEnhancement.ts","lineNumber":362,"sourceCode":"    // has since started). So finally, recreate the native \"scroll to hash\" behavior.\n    const hashPosition = internalDestinationHref.indexOf('#');\n    if (hashPosition >= 0) {\n      const hash = internalDestinationHref.substring(hashPosition + 1);\n      const targetElem = document.getElementById(hash);\n      targetElem?.scrollIntoView();\n    }\n\n    performingEnhancedPageLoad = false;\n    navigationEnhancementCallbacks.enhancedNavigationCompleted();\n\n    // For non-GET requests, the destination has to be the same URL you're already on, or result in a redirection\n    // (post/redirect/get). You're not allowed to POST to a different URL without redirecting, because then back/forwards\n    // won't work - we can't recreate the \"Resubmit form?\" behavior.\n    // See https://github.com/dotnet/aspnetcore/issues/50945\n    // The reason we delay throwing until after SSR completes is that SSR might include a redirection signal. If we get\n    // here without navigating away, it's an error.\n    if (isNonRedirectedPostToADifferentUrlMessage) {\n      throw new Error(isNonRedirectedPostToADifferentUrlMessage);\n    }\n  }\n}\n\nasync function getResponsePartsWithFraming(responsePromise: Promise<Response>, abortSignal: AbortSignal, onInitialDocument: (response: Response, initialDocumentText: string) => void, onStreamingElement: (streamingElementMarkup) => void) {\n  let response: Response;\n\n  try {\n    response = await responsePromise;\n\n    if (!response.body) { // Not sure how this can happen, but the TypeScript annotations suggest it can\n      onInitialDocument(response, '');\n      return;\n    }\n\n    const frameBoundary = response.headers.get('ssr-framing');\n    if (!frameBoundary) {\n      // Shouldn't happen, but perhaps some proxy stripped the headers. In that case we just won't respect streaming and will","sourceCodeStart":344,"sourceCodeEnd":380,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/294cab2f9b2e03af6b953820c7ab497c3c8b7ad9/src/Components/Web.JS/src/Services/NavigationEnhancement.ts#L344-L380","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"<!-- before -->\n<form method=\"post\" data-enhance action=\"/save\">\n\n<!-- after: PRG pattern -->\n<form method=\"post\" data-enhance action=\"/save\">\n<!-- server: return Redirect(\"/saved\") after processing -->","handlingStrategy":"validation","validationCode":"function followsPrgPattern(form: HTMLFormElement): boolean {\n  const method = (form.getAttribute('method') || 'get').toLowerCase();\n  if (method === 'get') return true;\n  // POST forms should not change the URL path unless they redirect\n  try {\n    const u = new URL(form.getAttribute('action') || form.action, document.baseURI);\n    return isForSamePath(u.href, location.href);\n  } catch { return false; }\n}","typeGuard":"function isUrlChangingPost(form: HTMLFormElement): boolean {\n  const method = (form.getAttribute('method') || 'get').toLowerCase();\n  if (method === 'get') return false;\n  try {\n    const u = new URL(form.getAttribute('action') || form.action, document.baseURI);\n    return !isForSamePath(u.href, location.href);\n  } catch { return true; }\n}","tryCatchPattern":"try {\n  await performEnhancedPageLoad(url, false, fetchOptions);\n} catch (e) {\n  if (/changes the URL/.test((e as Error).message)) {\n    // instruct server to follow Post/Redirect/Get, or drop data-enhance\n  } else { throw e; }\n}","preventionTips":["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."],"tags":["blazor","enhanced-nav","forms","post-redirect-get","history","navigation"],"analyzedSha":"294cab2f9b2e03af6b953820c7ab497c3c8b7ad9","analyzedAt":"2026-08-06T20:08:02.189Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}