-
-
Notifications
You must be signed in to change notification settings - Fork 10.8k
Add support for <Link unstable_rewrite> #14716
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
base: dev
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: 11e3773 The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
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 |
| "window.history.replaceState({ ...window.history.state, rewrite: undefined }, null);", | ||
| "}", | ||
| ].join("") | ||
| : ""; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we cannot support history.state-driven rewrites during SSR, we just clear out the rewrite location on SSR renders so the client just works with the normal browser URL location to avoid hydration issues.
| push(to, state) { | ||
| action = Action.Push; | ||
| let nextLocation = createMemoryLocation(to, state); | ||
| let nextLocation = isLocation(to) ? to : createMemoryLocation(to, state); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accept Locations in our history.push/replace API so that we can just proxy the rewrite along in the location
| ...initialLocation, | ||
| ...initialLocation.rewrite, | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hydrate the rewrite location if it exists
| ? // `matchRoutes()` has already been called if we're in here via `router.initialize()` | ||
| state.matches | ||
| : matchRoutes(routesToUse, location, basename); | ||
| : matchRoutes(routesToUse, routerPath, basename); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Matching/data loading operate against the routerPath, which is the rewrite if it exists
| !(opts && opts.submission && isMutationMethod(opts.submission.formMethod)) | ||
| ) { | ||
| completeNavigation(location, { matches }, { flushSync }); | ||
| completeNavigation(externalLocation, { matches }, { flushSync }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But when we complete the navigation, we do it to the external/user-facing to location
| search: state.location.rewrite.search, | ||
| hash: state.location.rewrite.hash, | ||
| }; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: I think this should probably move deeper now that rewrite lives on location and isn't just a magic field in location.state
| @@ -1,9 +1,25 @@ | |||
| import { useLoaderData, useLocation } from "react-router"; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
playground/framework is updated with an example usage - can toggle between ssr:true/ssr:false to see the differences in behavior on hard reloads
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: Revert these playground changes before merging
|
@brophdawg11 Just tested it for the Unsplash asset page modal and it seems to work perfectly. 😘 |
|
An interesting edge case I just encountered:
const location = useLocation();
const [searchParams] = useSearchParams();
searchParams.set('foo', 'bar');
return <Link to={{ pathname: location.pathname, search: searchParams.toString() }}>
Add query param
</Link>Result: The old modal setup (using location state) didn't have this problem. The URL in this case would be TanStack has declarative route masking. I wonder if this would help: https://tanstack.com/router/v1/docs/framework/react/guide/route-masking##declarative-route-masking For context, where this shows up in Unsplash is our (nested) modals: Screen.Recording.2026-01-09.at.16.13.58.mov |
|
Something else I noticed is that the location exposed by https://stackblitz.com/edit/github-p8mgs2ph?file=src%2Fapp.tsx
|
|
nvm got my wires crossed. That's because of this quick hack: #14716 (comment) Just to clarify - do you want |
I expected both the hook and |
|
Side note: I wonder if it's easier to think about this feature in terms of "route masking" (like TanStack Router) rather than "rewriting". It flips them around: Rewriting: <Link to="/photos/abc" rewrite="/?photo=abc" />Route masking: <Link to="/?photo=abc" mask="/photos/abc" /> |
|
@OliverJAsh I haven't forgotten about this - been noodling on it and playing with it a bit on and off in between some other work. I think you're right that what we're doing here is more "masking" then "rewriting". The router continues to operate on the
This makes sense and I think is fixed if we invert to a "mask" approach. To clarify, in the old setup - you had to manually proxy along |
Yeah exactly.
I'm curious how the "mask" approach would solve this. The link is relative to the current location, so how would you carry along the mask? If the user is on the page const location = useLocation();
const [searchParams] = useSearchParams();
searchParams.set('foo', 'bar');
return <Link to={{ pathname: location.pathname, search: searchParams.toString() }}>
Add query param
</Link> |
.changeset/silly-badgers-cough.md
Outdated
| "react-router": patch | ||
| --- | ||
|
|
||
| [UNSTABLE] Add support for `<Link unstable_rewrite>` which allows users to navigate to one URL in the browser but "rewrite" the url that is processed by the router, permitting contextual routing usages such as displaying an image in a model on top of a gallery |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: Needs to be updated for inverted mask approach
| relative?: RelativeRoutingType; | ||
|
|
||
| /** | ||
| * Rewrite path to "rewrite" the URL the router navigates to internally, while |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: Update for inverted mask approach
|
yeah I think I got my wires crossed - that's what I get trying to close the loop on something too quickly at the end of the day on a Friday 🤦♂️
I think in the end I was confused by this statement. I thought this meant that in your old setup, if the params got added to the correct (browser) URL then the masking/background location automatically carried along. But as I was digging into our old v6 declarative modal example I realized that wouldn't be possible either - which is why I was trying to confirm the manual proxying along of the background location.
I think that's where I'm landing. I don't think there's any way for us to intelligently decide which location (router or masked) any new I pushed up the changes to invert this to pnpm i
pnpm build
cd playgrounds/framework
pnpm dev |
|
Oops - forgot to do an updated experimental from this branch: |

RFC: #9864
Add support for
<Link unstable_rewrite>which allows users to navigate to one URL in the browser but "rewrite" the url that is processed by the router, permitting contextual routing usages such as displaying an image in a model on top of a gallery.This brings the long-standing example of doing this manually in declarative mode into Data/Framework Mode for client side navigations.