Connected-react-router: Server-side rendering doesn't work

Created on 20 Dec 2018  Â·  12Comments  Â·  Source: supasate/connected-react-router

When using SSR with ReactDOMServer.renderToString I get following exception:

    Invariant Violation: Could not find "store" in the context of "Connect(ConnectedRouterWithContext)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(ConnectedRouterWithContext) in connect options.
        at invariant (/home/user/app/node_modules/invariant/invariant.js:40:15)
        at Connect.renderWrappedComponent (/home/user/app/node_modules/react-redux/lib/components/connectAdvanced.js:162:32)
        at ReactDOMServerRenderer.render (/home/user/app/dist/node_modules/react-dom/cjs/react-dom-server.node.development.js:2494:55)
        at ReactDOMServerRenderer.read (/home/user/app/dist/node_modules/react-dom/cjs/react-dom-server.node.development.js:2357:19)
        at Object.renderToString (/home/user/app/dist/node_modules/react-dom/cjs/react-dom-server.node.development.js:2729:25)
        at renderToString (/home/user/app/dist/server/webpack:/src/server/indexServer.js:220:18)

Dependencies:

    "connected-react-router": "6.0.0",
    "react": "16.7.0",
    "react-dom": "16.7.0",
    "react-redux": "6.0.0",
    "react-router-dom": "4.3.1",
    "redux": "4.0.1",

Compoents:

    <Provider store={store}>
      <ErrorCatcher>
        <ConnectedRouter history={history}>
          <Switch>
            <Route path="/recorder/" component={Recorder} />
            <Route path="/" component={Regular} />
          </Switch>
        </ConnectedRouter>
      </ErrorCatcher>
    </Provider>

Rendering client-side only works.

Most helpful comment

Me too, without SSR

All 12 comments

Minimal testcase:

import ReactDOMServer from 'react-dom/server';
import React from 'react';
import { Provider } from 'react-redux';
import { ConnectedRouter, push } from 'connected-react-router';
import { createStore } from 'redux';
import createHistory from 'history/createMemoryHistory';

const history = createHistory();
const store = createStore(() => {});

console.log('DUMP', ReactDOMServer.renderToString((
  <Provider store={store}>
    <ConnectedRouter history={history}>
      <div>Hello</div>
    </ConnectedRouter>
  </Provider>
)));

You may want to use <StaticRouter> on the server side. Here's an example from a repo that adds SSR to a non-ejected CRA starter app. https://github.com/cereallarceny/cra-ssr/blob/master/server/loader.js

I'm facing the same issue without SSR.

Mee too

Me too, without SSR

same issue when using ssr

I'm using the connected-react-router v6 and still having the issue using SSR.

Just moved off of react-router to connected-react-router v6.(.3..2). We have an SSR setup and are running into the same issue.

When using SSR with ReactDOMServer.renderToString I get following exception:

    Invariant Violation: Could not find "store" in the context of "Connect(ConnectedRouterWithContext)". Either wrap the root component in a <Provider>, or pass a custom React context provider to <Provider> and the corresponding React context consumer to Connect(ConnectedRouterWithContext) in connect options.
        at invariant (/home/user/app/node_modules/invariant/invariant.js:40:15)
        at Connect.renderWrappedComponent (/home/user/app/node_modules/react-redux/lib/components/connectAdvanced.js:162:32)
        at ReactDOMServerRenderer.render (/home/user/app/dist/node_modules/react-dom/cjs/react-dom-server.node.development.js:2494:55)
        at ReactDOMServerRenderer.read (/home/user/app/dist/node_modules/react-dom/cjs/react-dom-server.node.development.js:2357:19)
        at Object.renderToString (/home/user/app/dist/node_modules/react-dom/cjs/react-dom-server.node.development.js:2729:25)
        at renderToString (/home/user/app/dist/server/webpack:/src/server/indexServer.js:220:18)

Dependencies:

    "connected-react-router": "6.0.0",
    "react": "16.7.0",
    "react-dom": "16.7.0",
    "react-redux": "6.0.0",
    "react-router-dom": "4.3.1",
    "redux": "4.0.1",

Compoents:

    <Provider store={store}>
      <ErrorCatcher>
        <ConnectedRouter history={history}>
          <Switch>
            <Route path="/recorder/" component={Recorder} />
            <Route path="/" component={Regular} />
          </Switch>
        </ConnectedRouter>
      </ErrorCatcher>
    </Provider>

Rendering client-side only works.

@zdila My solution is following:

In the store:

export const getStoreConfiguration = (preloadedState) => {
  const middlewares = [];

  let history;
  let reducers = {
    init(state, action) {
      return { ...state, ...action.payload };
    },
  };

  if (isClient()) {
    history = createBrowserHistory();
    reducers.router = connectRouter(history);
    middlewares.push(routerMiddleware(history));
  }

  return {
    store: createStore(
      combineReducers(reducers),
      preloadedState,
      composeEnhancer(applyMiddleware(...middlewares))
    ),
    history,
  };
};

isClient

export default () => {
  try {
    if (window && window.document && window.document.createElement) return true;

    return false;
  } catch (err) {
    return false;
  }
}

in the client

const { store, history } = getStoreConfiguration(preloadedState);
<Provider store={store}>
    <ConnectedRouter history={history}>
      <ClientApp />
    </ConnectedRouter>
  </Provider>,


The key is check the client or server, for difference env to create the store and history.

@zsirfs I am using solution provided in https://github.com/supasate/connected-react-router/issues/212#issuecomment-449390810. But still old approach used to work in older versions of connected-react-router (react-router-redux).

@zsirfs I am using solution provided in #212 (comment). But still old approach used to work in older versions of connected-react-router (react-router-redux).

Ok, I am using my solution work well.

I have an older project from 2015, but got it working. Newer projects would probably work the same.

I had to use StaticRouter server-side 👎, but it worked when I made connectedRouter optional in combineReducers:

const createRootReducer = history => (
  combineReducers({
    collapsibleMenu: collapsibleMenuReducer,
    pageMeta: pageMetaReducer,
    playback: playbackReducer,
    ...(
      history
      && { router: connectRouter(history) }
    ),
    showSongs: showSongsReducer,
  })
)

Then, in my server renderer, I did this:

const context = {}
const store = compose()(createStore)(createRootReducer())

const renderedContent = renderToString(
  <Provider store={store}>
    <StaticRouter
      context={context}
      location={req.url}
    >
      <Pages />
    </StaticRouter>
  </Provider>
)

const renderedPage = renderSite(renderedContent, store.getState())

if (context.url) {
  console.log('redirect');
  res.writeHead(302, { Location: context.url })
  res.end()
}
else {
  console.log(req.url);
  res.send(renderedPage).end()
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

AdrienLemaire picture AdrienLemaire  Â·  5Comments

bmueller-sykes picture bmueller-sykes  Â·  4Comments

preciselywilliam picture preciselywilliam  Â·  4Comments

alamchrstn picture alamchrstn  Â·  5Comments

zry754331875 picture zry754331875  Â·  3Comments