Route masking is a way to mask the actual URL of a route that gets persisted to the browser's history and URL bar. This is useful for scenarios where you want to show a different URL than the one that is actually being navigated to and then falling back to the displayed URL when it is shared and (optionally) when the page is reloaded. Here's a few examples:
Each of these scenarios can be achieved with route masking and even extended to support more advanced patterns like parallel routes.
You do not need to understand how route masking works in order to use it. This section is for those who are curious about how it works under the hood. Skip to How do I use route masking? to learn how to use it!.
Route masking utilizes the API to store the desired runtime location inside of the location that will get written to the URL. It stores this runtime location under the state property:
const location = {
pathname: '/photos/5',
search: '',
hash: '',
state: {
key: 'wesdfs',
__tempKey: 'sadfasd',
__tempLocation: {
pathname: '/photo/5/modal',
search: '',
hash: '',
state: {},
},
},
}const location = {
pathname: '/photos/5',
search: '',
hash: '',
state: {
key: 'wesdfs',
__tempKey: 'sadfasd',
__tempLocation: {
pathname: '/photo/5/modal',
search: '',
hash: '',
state: {},
},
},
}When the router parses a location from history with the property, it will use that location instead of the one that was parsed from the URL. This allows you to navigate to a route like and have the router actually navigate to instead. When this happens, the history location is saved back into the property, just in case we need to know what the actual URL is. One example of where this is used is in the Devtools where we detect if a route is masked and show the actual URL instead of the masked one!
Remember, you don't need to worry about any of this. It's all handled for you automatically under the hood!
Route masking is a simple API that can be used in 2 ways:
When using either route masking APIs, the option accepts the same navigation object that the and APIs accept. This means you can use the same , , , and options that you're already familiar with. The only difference is that the option will be used to mask the URL of the route being navigated to.
🧠The mask option is also type-safe! This means that if you're using TypeScript, you'll get type errors if you try to pass an invalid navigation object to the option. Booyah!
The and APIs both accept a option that can be used to mask the URL of the route being navigated to. Here's an example of using it with the component:
<Link
to="/photos/$photoId/modal"
params={{ photoId: 5 }}
mask={{
to: '/photos/$photoId',
params: {
photoId: 5,
},
}}
>
Open Photo
</Link><Link
to="/photos/$photoId/modal"
params={{ photoId: 5 }}
mask={{
to: '/photos/$photoId',
params: {
photoId: 5,
},
}}
>
Open Photo
</Link>And here's an example of using it with the API:
const navigate = useNavigate()
function onOpenPhoto() {
navigate({
to: '/photos/$photoId/modal',
params: { photoId: 5 },
mask: {
to: '/photos/$photoId',
params: {
photoId: 5,
},
},
})
}const navigate = useNavigate()
function onOpenPhoto() {
navigate({
to: '/photos/$photoId/modal',
params: { photoId: 5 },
mask: {
to: '/photos/$photoId',
params: {
photoId: 5,
},
},
})
}In addition to the imperative API, you can also use the Router's option to declaratively mask routes. Instead of needing to pass the option to every or call, you can instead create a route mask on the Router to mask routes that match a certain pattern. Here's an example of the same route mask from above, but using the option instead:
import { createRouteMask } from '@tanstack/react-router'
const photoModalToPhotoMask = createRouteMask({
routeTree,
from: '/photos/$photoId/modal',
to: '/photos/$photoId',
params: (prev) => ({
photoId: prev.photoId,
}),
})
const router = createRouter({
routeTree,
routeMasks: [photoModalToPhotoMask],
})import { createRouteMask } from '@tanstack/react-router'
const photoModalToPhotoMask = createRouteMask({
routeTree,
from: '/photos/$photoId/modal',
to: '/photos/$photoId',
params: (prev) => ({
photoId: prev.photoId,
}),
})
const router = createRouter({
routeTree,
routeMasks: [photoModalToPhotoMask],
})When creating a route mask, you'll need to pass 1 argument with at least:
🧠The option is also type-safe! This means that if you're using TypeScript, you'll get type errors if you try to pass an invalid route mask to the option.
URLs are automatically unmasked when they are shared since as soon as a URL is detached from your browsers local history stack, the URL masking data is no longer available. Essentially, as soon as you copy and paste a URL out of your history, its masking data is lost... after all, that's the point of masking a URL!
By default, URLs are not unmasked when the page is reloaded locally. Masking data is stored in the property of the history location, so as long as the history location is still in memory in your history stack, the masking data will be available and the URL will continue to be masked.
As stated above, URLs are not unmasked when the page is reloaded by default.
If you want to unmask a URL locally when the page is reloaded, you have 3 options, each overriding the previous one in priority if passed: