An important part of "routing" is handling redirects. Redirects usually happen when you want to preserve an old link and send all the traffic bound for that destination to some new URL so you don't end up with broken links.
The way we recommend handling redirects has changed in React Router v6. This document explains why.
In React Router v4/5 (they have the same API, you can read about why we had to bump the major version here) we had a <Redirect> component that you could use to tell the router when to automatically redirect to another URL. You might have used it like this:
import { Switch, Route, Redirect } from "react-router-dom";
function App() {
return (
<Switch>
<Route path="/home">
<HomePage />
</Route>
<Redirect from="/" to="/home" />
</Switch>
);
}In React Router v4/5, when the user lands at the / URL in the app above, they are automatically redirected to /home.
There are two common environments in which React Router usually runs:
- In the browser
- On the server using React's node.js API
In the browser a <Redirect> is simply a history.replaceState() on the initial render. The idea is that when the page loads, if you're at the wrong URL, just change it and rerender so you can see the right page. This gets you to the right page, but also has some issues as we'll see later.
On the server you handle redirects by passing an object to <StaticRouter context> when you render. Then, you check context.url and context.status after the render to see if a <Redirect> was rendered somewhere in the tree. This generally works fairly well, except you have to invoke ReactDOMServer.renderToString(...) just to know if you need to redirect, which is less than ideal.
As mentioned above, there are a few problems with our redirect strategy in React Router v4/5, namely:
- "Redirecting" in the browser isn't really redirecting. Your server still served up a valid HTML page with a 200 status code at the URL originally requested by the client. If that client was a search engine crawler, it got a valid HTML page and assumes it should index the page. It doesn't know anything about the redirect because the page was served with a 200 OK status code. This hurts your SEO for that page.
- Invoking
ReactDOMServer.renderToString()on the server just to know if you need to redirect or not wastes precious resources and time. Redirects can always be known ahead of time. You shouldn't need to render to know if you need to redirect or not.
So we are rethinking our redirect strategy in React Router v6 to avoid these problems.
Our recommendation for redirecting in React Router v6 really doesn't have much to do with React or React Router at all. It is simply this: if you need to redirect, do it on the server before you render any React and send the right status code. That's it.
To handle the situation above, your server code might look something like this (using the Express API):
import * as React from "react";
import * as ReactDOMServer from "react-dom/server";
import { StaticRouter } from "react-router-dom/server";
import App from "./App";
function handleExpressRequest(req, res) {
// Handle redirects *before* you render and save yourself some time!
if (req.url === "/") {
return res.redirect("/home");
}
let html = ReactDOMServer.renderToString(
<StaticRouter location={req.url}>
<App />
</StaticRouter>
);
res.end(html);
}If you aren't server rendering your app you can still redirect on the initial render in the client like this:
import { Routes, Route, Navigate } from "react-router-dom";
function App() {
return (
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/" element={<Navigate replace to="/home" />} />
</Routes>
);
}In the above example, when someone visits /, they will automatically be redirected to /home, same as before.
Please note however that this won't work when server rendering because the navigation happens in a React.useEffect().
The new <Navigate> element in v6 works like a declarative version of the useNavigate() hook. It's particularly handy in situations where you need a React element to declare your navigation intent, like <Route element>.
The <Navigate replace> prop tells the router to use history.replaceState() when updating the URL so the / entry won't end up in the history stack. This means that when someone clicks the back button, they'll end up at the page they were at before they navigated to /.