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.
Important
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 location.state 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 __tempLocation 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 location.state.__tempLocation 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 /photos/5 and have the router actually navigate to /photo/5/modal instead. When this happens, the history location is saved back into the location.maskedLocation 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 mask option accepts the same navigation object that the <Link> and navigate() APIs accept. This means you can use the same to, replace, state, and search options that you're already familiar with. The only difference is that the mask 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 mask option. Booyah!
The <Link> and navigate() APIs both accept a mask option that can be used to mask the URL of the route being navigated to. Here's an example of using it with the <Link> 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 navigate() API:
const navigate = useNavigate()
function onOpenPhoto() {
navigate({
to: '/photos/$photoId/modal',
mask: {
to: '/photos/$photoId'
params: {
photoId: 5,
},
}
})
}
const navigate = useNavigate()
function onOpenPhoto() {
navigate({
to: '/photos/$photoId/modal',
mask: {
to: '/photos/$photoId'
params: {
photoId: 5,
},
}
})
}
In addition to the imperative API, you can also use the Router's routeMasks option to declaratively mask routes. Instead of needing to pass the mask option to every <Link> or navigate() 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 routeMasks option instead:
// Use the following for the example below
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 createRouteMask 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 routeMasks 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 location.state 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:
Your weekly dose of JavaScript news. Delivered every Monday to over 100,000 devs, for free.