Migration testing · SEO
Follow the submission all the way to its result
A small experiment separates a working address, an intact request and an operation that actually happened.
- A reachable destination can receive an empty requestIn our test, POST became GET after 301, 302 and 303. The final response was still 200 OK.
- The later hop can erase the earlier guaranteeA 307 preserved our submission only as far as the next 301. The final receiver got no body.
- A receipt is allowed to be a page visitOur save-then-303 case retained the submitted values before opening the receipt with GET. Processing order decides the right test.
Picture a migration review in which every old address reaches the expected new address. The redirect report is green. Someone opens the contact page, sees the familiar form and approves the release. But the form still submits to an old handler, and a redirect changes the submission before the new handler receives it.
That is a hypothetical failure scenario. We built a small local experiment to test its mechanism: five redirect codes, two request methods, one mixed chain and one deliberate save-before-redirect case. Every test finished with a 200 response. They did not deliver equivalent requests.
The finding changes what belongs in a migration handoff. A URL mapping describes where a visitor should arrive. A form also needs a description of what the receiving application should do with the values that visitor supplied. A page can move successfully while that operation stops working.
A permanent move leaves the request method undecided
Google’s redirect documentation lists both 301 and 308 as permanent redirects that signal a preferred target for canonicalization. There is therefore no Google-documentation basis for treating 301 as the only permanent option. This classification does not establish that the two codes preserve an application request in the same way, or guarantee an identical ranking outcome.
The Fetch standard’s redirect algorithm supplies the missing distinction. For a POST encountering 301 or 302, it changes the method to GET and removes the body. A 303 makes that change for methods other than GET or HEAD. Our controlled client followed those rules.
For a form that submits values in the request body, losing that body matters even when the address is correct. Preserving a query string is a different observation: values after the question mark in a URL are not evidence that values sent inside a POST survived.
Ten green responses concealed two kinds of arrival
We used the built-in fetch client in Node 22.14.0, with Undici 6.21.1, on Windows. A temporary server accepted requests on one loopback origin. Each redirect endpoint sent the client to the same receiver, which recorded the arriving method and body and returned 200 regardless of what it received. This deliberately permissive receiver made the false positive visible.
The synthetic POST body was case=redirect-probe&value=alpha, sent as application/x-www-form-urlencoded. It contained 31 ASCII bytes. We compared the complete received string, rather than accepting a matching length as proof that the values survived. No customer information or real submission endpoint was involved.
What the receiver got after a POST
| Redirect | Arriving method | Body received |
|---|---|---|
| 301 | GET | 0 bytes |
| 302 | GET | 0 bytes |
| 303 | GET | 0 bytes |
| 307 | POST | All 31 bytes, exact match |
| 308 | POST | All 31 bytes, exact match |
The five GET controls explain why checking old addresses alone can miss the defect. In this fixture, their final status, method and empty body were indistinguishable across all five redirect codes. The POST cases exposed a difference that was absent from the GET input. This was a tiny controlled probe, not a test of a named SEO crawler or an entire migration audit product.

There is a related but separate limitation in soft 404 diagnosis: a successful HTTP response does not settle whether meaningful page content exists. Here, the final page could be perfectly useful. The unanswered question concerns the submitted operation.
A later 301 undoes an earlier 307
Our eleventh case added a second redirect. The first endpoint returned 307. Its target received POST with the full 31-byte body, then returned 301. Only after that second response did the client change to GET and drop the body. The saved trace was:
POST /chain 31 bytes
307 to /r/301
POST /r/301 31 bytes
301 to /receive
GET /receive 0 bytes
200 OK
A review that checks only the first response would describe this as a method-preserving redirect. A review that checks only the final response would describe it as a successful arrival. Both descriptions omit the hop that determines whether the receiver can process the original submission.
A real deployment could place different routing decisions in a proxy, an application or a slash-normalization rule. We did not test those products. The observed chain is enough to justify a narrower requirement: record the method and destination at every hop, then verify the body at the handler that is supposed to use it.
The correct 303 begins with a saved record
Changing every redirect to 308 would be the wrong lesson. Our twelfth case gave the first endpoint a different job. It read the POST body, saved the exact string in memory as receipt 1, and only then returned 303 to a receipt endpoint. The client fetched that receipt with GET and no body. The saved count was one, and the saved value exactly matched the original submission.
RFC 9110 describes 303 as a way to retrieve a separate resource providing an indirect response to the original request. It also distinguishes the temporary method-preserving 307 from the permanent 308 alternative to 301. These choices answer different questions about the application’s next step.
Our receipt lived only in the test process’s memory. That demonstrates ordering and exact value retention, not database durability, duplicate prevention or a production checkout. On a real form, the corresponding evidence should be the application record that the workflow promises to create. A thank-you page alone can be displayed without that record.
Give the migration handoff an operation to prove
For a form-owning team, a practical acceptance test starts with one representative staging workflow and a clearly identified expected result. Keep the ordinary URL checks, then add the following evidence for the operation:
- Name the form and its actual submission target. Record the rendered action or the request sent by the application. Opening the page that contains the form does not exercise its handler.
- Submit distinct synthetic values through the intended client. Use a staging record you can recognize. Keep the method, content type and expected values with the test, so the receiver can be checked against a concrete input.
- Inspect the complete redirect sequence. Identify where the method changes. Compare the exact values at the processing endpoint, including any redirect introduced before the application receives the request.
- Verify the expected saved result. Check the record or state change promised by that workflow. If processing precedes a 303, inspect the saved result behind the receipt; do not reject it merely because the receipt uses GET.
- Write a specific acceptance statement. For example: this staging submission created one expected record with these values, through this client and these hops. Keep browser, authentication and cross-origin checks explicit when they are part of the real route.
A specialized protocol can impose stricter requirements. The one-click unsubscribe workflow has its own rules, including a prohibition on redirecting that endpoint. Generic form-routing advice cannot override them. Likewise, a status code returned for robots.txt answers a different question from the same code returned for a content page: the resource and operation belong in the diagnosis.
The useful handoff ends with a recorded outcome. “Old URL reaches new URL” remains a valid claim about navigation. A form owner needs the additional evidence that the intended values reached the processing step and produced the intended result.
Protocol sources and experiment scope
Primary documentation checked on 14 September 2026. Original observations come from 12 requests in one local Node 22.14.0 / Undici 6.21.1 fixture, with a replayable string body and automatic redirect following. The server and client were closed after the test. Browser form submission, Googlebot behavior, cross-origin credentials, CDNs and live conversions were not measured.




