V5.0.1
https://github.com/modsoussi/react-ssr
git clone https://github.com/modsoussi/react-ssr.gitcd react-ssrnpm inpm startlocalhost:5000/Expect app to render. This is the code for App:
class App extends React.Component {
render() {
return (
<div>
<nav>
<ul>
<li><Link to="/">Hello</Link></li>
<li><Link to="/bye">Bye</Link></li>
</ul>
</nav>
<Route path="/" exact component={Hello} />
<Route path="/bye" component={Bye} />
</div>
)
}
}
On the client, it's rendered inside a BrowserRouter, and on the server it's rendered inside a StaticRouter.
hydrate(
<BrowserRouter>
<App />
</BrowserRouter>, document.getElementById('app-root'));
const component = require('./dist');
const context = {};
const app = React.createElement(
StaticRouter,
{ location: req.url, context: context },
React.createElement(component)
);
const markup = renderToStaticMarkup(app);
renderToStaticMarkup(app) fails and throws Error: Invariant failed: You should not use <Link> outside a <Router>.
Similar issue - fixed by downgrading react-router-dom to 5.0.0.
Downgrading react-router-dom to 5.0.0 didn't work
OK, so you're using two instances of the Router context pair. (Side note: I'll explain things fully here, but I'm going to have to turn this into a saved response...)
There is a new React.createContext API that we use in 5.0 to replace the legacy context usage. This involves creating a "context" pair, a set of Provider and Consumer components. We create that in a singleton module that is imported into Router and Link directly. In this new API, you have to use the _exact_ same instance. Two separately-created contexts will never match up and won't be visible to each other.
In your server, you're importing from your build output and from the react-router-dom module. This means that within your bundle, it's importing the bundle copy of context and in the server render, it's importing directly from the module (in node_modules). This results in the Provider in Router not being visible to the Consumer in Link.
How you solve this is up to you. The most ideal option is to not import your bundle, but import from src directly. (Note: that might involve using @babel/register if you're using JS features incompatible with your version of Node.) Another option is to export react-router-dom from your bundle and use that in your server renderer so that you have the same context pair. I can't tell you what is the best option, since you need to evaluate any tradeoffs.
One thing to add: This isn't fundamentally a React Router or even a React problem. Anything that uses a singleton module would be affected by this if you reference that singleton in your bundle and your server separately. It's the nature of JS itself, so the fixes I suggest are best for avoiding problems with other libraries as well.
Take a look at my answer for a possible solution here:
https://github.com/ReactTraining/react-router/issues/6769#issuecomment-505435886
Exporting StaticRouter from my bundle did the trick. I import it and use it in my server's code.
Most helpful comment
OK, so you're using two instances of the Router context pair. (Side note: I'll explain things fully here, but I'm going to have to turn this into a saved response...)
There is a new
React.createContextAPI that we use in 5.0 to replace the legacy context usage. This involves creating a "context" pair, a set of Provider and Consumer components. We create that in a singleton module that is imported into Router and Link directly. In this new API, you have to use the _exact_ same instance. Two separately-created contexts will never match up and won't be visible to each other.In your server, you're importing from your build output and from the
react-router-dommodule. This means that within your bundle, it's importing the bundle copy of context and in the server render, it's importing directly from the module (innode_modules). This results in the Provider in Router not being visible to the Consumer in Link.How you solve this is up to you. The most ideal option is to not import your bundle, but import from
srcdirectly. (Note: that might involve using@babel/registerif you're using JS features incompatible with your version of Node.) Another option is to exportreact-router-domfrom your bundle and use that in your server renderer so that you have the same context pair. I can't tell you what is the best option, since you need to evaluate any tradeoffs.One thing to add: This isn't fundamentally a React Router or even a React problem. Anything that uses a singleton module would be affected by this if you reference that singleton in your bundle and your server separately. It's the nature of JS itself, so the fixes I suggest are best for avoiding problems with other libraries as well.