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

Add a new dataRedirect utility for external redirects on .data requests #13364

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from

Conversation

brophdawg11
Copy link
Contributor

@brophdawg11 brophdawg11 commented Apr 4, 2025

Background: In order to trigger a redirect navigation from a client side fetch, we have to use something other than a 3xx response because a 3xx will redirect the fetch call. We need to return some form of "data" that we can look at and decide to perform a soft application redirect.

Prior to single fetch, we used to return 204 responses with X-Remix-Redirect headers. These worked well and used web standards so while the 204 code and custom header were "implementation details", folks could use them if/when needed to perform redirects on _data requests from outside of Remix - i.e., in an express middleware.

With single fetch, we switched to encoding the redirect into the body of the response so we could encode them in prerendered .data files as well. This means that a redirect on a .data request relies on the implementation detila of turbo-stream's encoding format.

This made redirects on .data requests from outside of React Router much tricker, since they now need to encode a turbo-stream response body and use the custom symbol we use internally (#12693).

Options: We have 2 options if we want to re-enable this functionality:

  • Expose a helper function that encodes a redirect via turbo-stream
    • ❌ I think this is a non-starter because when we move to the RSC format the utility won't be be future flag aware so it won't know the proper way to encode the body
  • ✅ Add back support for the 204 responses

This PR adds back support for those 204 responses, and it also introduces a dataRedirect utility (bikeshedding welcome) for constructing them so we don't have to leak our custom header implementation details.

Alternatively, another option now that the redirect util has forked a few times (for replace and reloadDocument navigations) is to extend ResponseInit and collapse those back into a single utility:

export type RedirectFunction = (
  url: string,
  init?:
    | number
    | (ResponseInit & {
        rr: {
          replace?: boolean;
          reloadDocument?: boolean;
          revalidate?: boolean;
          dataRequest?: boolean; // soft nav?  clientSide?  ...
        }
      })
) => Response;

We originally chose not to extend onto ResponseInit like that but there is some precedent for it: https://developers.cloudflare.com/workers/runtime-apis/response/#parameters

Copy link

changeset-bot bot commented Apr 4, 2025

🦋 Changeset detected

Latest commit: e15116a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 11 packages
Name Type
react-router Major
@react-router/architect Major
@react-router/cloudflare Major
@react-router/dev Major
react-router-dom Major
@react-router/express Major
@react-router/node Major
@react-router/serve Major
@react-router/fs-routes Major
@react-router/remix-routes-option-adapter Major
create-react-router Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment on lines +1487 to +1491
res.statusMessage = response.statusText;
res.status(response.status);
for (let [key, value] of response.headers.entries()) {
res.append(key, value);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't export our internal logic for converting a fetch Response to an express res so for now it's assumed that the application has to do that adapter logic on their own. I think this is ok since status/headers are all that matter? There isn't a body on these responses

} else {
return { status: SINGLE_FETCH_REDIRECT_STATUS, data };
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we get back a 204 redirect we convert it to a single fetch redirect here for downstream processing

}
if (result.replace) {
headers["X-Remix-Replace"] = "yes";
headers["X-Remix-Replace"] = "true";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Align these with values used elsewhere - value doesn't really matter since we use .has(header)

@brophdawg11 brophdawg11 added the review flag for team review label Apr 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed review flag for team review
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant