First, let's make sure we're talking about the same thing:
function App() {
return (
<BrowserRouter>
<Redirect from="/old-route" to="/new-route" />
{/* ... more routes etc... */}
</BrowserRouter>
)
}
(Or however else you might accomplish the same thing in your own framework).
I'm NOT talking about this kind of usage (example borrowed from the docs):
<Route exact path="/">
{loggedIn ? <Redirect to="/dashboard" /> : <PublicHomePage />}
</Route>
That's technically fine, though there may be a better way to handle this in your app. Read my post Authentication in React Applications for more about this.
Ok, so now that we're clear on that, let me explain why and then what you should be doing instead.
First, the goal of using a client-side redirect (like with Redirect's from
prop or similar) is to ensure the user doesn't land on a URL that you expect
they could land on and instead redirects them to a different one. A great
example of this is when you change the URL scheme of your app. Or perhaps you
sent an email out to your users with the wrong URL and now you're stuck
redirecting users for all eternity.
Whatever the case may be... If you know the URL you want to redirect users from, then you're better off redirecting them on the server. Let's talk about why client-side redirects are such a problem.
The 149.3 kilobyte redirect 😱
Let's say a user comes to your app and hits the /old-route
. Before the browser
knows it's on a bad route, they have to download your app. Regardless of how
good you are at code splitting, they're going to need at least react
,
react-dom
, and react-router-dom
. That's some bytes!
- react@16.13.1 - 6.3kB
- react-dom@16.13.1 - 114.6kB
- react-router-dom@5.1.2 - 28.4kB
These sizes are minified but uncompressed, because compression depends on too many variables for accurate comparisons. Also, I realize that React Router v6 is coming soon and will reduce the bundle size significantly, but hear me out!
And that doesn't even include your own code!
Now, let's not be disingenuous! <Redirect />
only supports redirecting within
the same app, so the user's going to need to download all of that stuff anyway.
But the concept still applies: Don't do a client-side redirect from a known
route to another known route. If it's from one app to another, then the user
just downloaded and ran a bunch of code they didn't need. Also, even if it's
within-app, then depending on how you have things configured with
code-splitting, it's definitely possible that this resulted in the user
downloading too much or kicking off a waterfall effect (resulting in suboptimal
performance).
As for using <Redirect />
when it is within your own app, the user will have
to wait until the app finishes loading and is actually run before the URL in
their address bar is updated to the correct URL. Maybe not reason enough by
itself to avoid <Redirect />
with the from
prop, but keep reading.
HTTP Status Codes
This is definitely reason enough on its own to avoid client-side redirects (like
<Redirect />
with the from
prop). When a user is redirect via JavaScript,
there's no way to inform the browser (or search engines) that the URL they were
on before is no longer correct and the new one is correct. This has implications
on search engines and
the browser cache
(also interesting cache handling for error status codes).
Server side redirects
I'll show you three server-side redirects that I implemented for
bookshelf.lol. In the bookshelf app, I don't have
anything at the home route of /
(note that the bookshelf app is purely for
educational purposes, so semi-contrived decisions like this are not to be
questioned. Please and thank you).
So instead, every request to /
should be redirected to /list
. And I want to
redirect with a 302
because it's quite possible that at some point in the
future I'll change my mind and I don't want to have to deal with busting the
browser or search engine cache of this particular redirect.
Netlify (production)
With Netlify, you can configure redirects in
a netlify.toml
file,
but I chose the _redirects
file
option. Here's what that file needs to get that redirect in place:
/ /list 302!
You can check out this file as it stands today right here.
If I wanted to use the netlify.toml
file, then it would be more like this:
[[redirects]]
from = "/"
to = "/list"
status = 302
force = true
Locally built file
Once I've built the bookshelf app, if I want to run it locally, I use
serve
to do so. This CLI uses
serve-handler
which can be configured with
serve.json
like so:
{
"redirects": [
{
"source": "/",
"destination": "/list",
"type": 302
}
]
}
You can check out this file as it stands today right here.
Locally during development
The bookshelf app is built with
Create React App (and it uses react-scripts
).
This means that the dev server is completely handled for me behind the scenes.
Luckily, react-scripts
does allow for customization of the server for proxy
purposes and I can use this for the redirect by creating a file at
src/setupProxy.js
. This simply exports a function which accepts the
express app and then I can use
res.redirect
when the /
route is requested:
function proxy(app) {
app.get(/^\/$/, (req, res) => res.redirect('/list'))
}
module.exports = proxy
You can check out this file as it stands today right here.
Conclusion
Whatever host you end up publishing your app to probably has some way to configure redirects (if it doesn't that's super weird of them). It may feel like a lot of work to avoid a simple:
<Redirect from="/" to="/list" />
But really the cost is pretty small (set it up once and forget about it) and the benefits of being semantically correct about this outweighs those costs.
In general, following semantics of the web is better for existing tools (just like how using semantic markup is better for accessibility). If the web standards satisfy the use case, then use the web standard.
Thank you to Ryan Florence (creator of React Router) who convinced me of this idea.