question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Trouble with server-side rendering

See original GitHub issue

My current saga setup works flawlessly on the client side but does not work at all on the server side 😕 . My current shared/redux/configureStore.js module is:

import { createStore, applyMiddleware, compose } from 'redux';
import { combineReducers } from 'redux-immutable';
import { fromJS } from 'immutable';
import { LOCATION_CHANGE } from 'react-router-redux';
import createSagaMiddleware, { END } from 'redux-saga';
import reduxCatch from 'redux-catch';
import postReducer from './modules/post';

function errorHandler(error, getState) {
  console.error(error);
  console.debug('current state', getState());
}

function routeReducer(state = fromJS({ locationBeforeTransitions: null }), action) {
  if (action.type === LOCATION_CHANGE) {
    return state.merge({
      locationBeforeTransitions: action.payload,
    });
  }
  return state;
}

export default function configureStore(initialState) {
  const sagaMiddleware = createSagaMiddleware();

  const store = createStore(combineReducers({ postReducer, route: routeReducer }), initialState, compose(
    applyMiddleware(sagaMiddleware, reduxCatch(errorHandler)),
    typeof window === 'object' && typeof window.devToolsExtension !== 'undefined' ? window.devToolsExtension() : f => f,
  ));
  if (module.hot) {
    // Enable Webpack hot module replacement for reducers
    module.hot.accept('./modules/post', () => {
      const nextReducer = require('./modules/post'); //eslint-disable-line
      store.replaceReducer(nextReducer);
    });
  }

  store.runSaga = sagaMiddleware.run; //eslint-disable-line
  store.close = () => store.dispatch(END); //eslint-disable-line
  return store;
}

and the section of the server/server.js that handles server-side rendering (which I’ve tried several approaches with in terms of running my “rootPostSaga” from shared/redux/modules/post.js) is:

// Server Side Rendering based on routes matched by React-router.
app.use((req, res) => {
  match({ routes, location: req.url }, (err, redirectLocation, renderProps) => {
    if (err) {
      return res.status(500).end('Internal server error');
    }

    if (!renderProps) {
      return res.status(404).end('Not found!');
    }

    const initialState = fromJS({
      postReducer: {
        posts: [],
        post: {},
      },
      route: {
        locationBeforeTransitions: null,
      },
    });

    const store = configureStore(initialState);
    store.runSaga(rootPostSaga).done.then(() => {
      fetchComponentData(store.dispatch, renderProps.components, renderProps.params)
        .then(() => {
          const createElement = (Component, props) => (
            <Component
              {...props}
              radiumConfig={{ userAgent: req.headers['user-agent'] }}
            />
          );
          const initialView = renderToString(
            <Provider store={store}>
              <RouterContext {...renderProps} createElement={createElement} />
            </Provider>
          );
          const finalState = store.getState().toJS();

          res.status(200).end(renderFullPage(initialView, finalState, getFilename()));
        })
        .catch((error) => {
          res.end(renderFullPage(`Error: ${error}`, {}, getFilename()));
        });
    });
    store.close();
  });
});

where “fetchComponentData” comes from:

/*
*  This was inspired from
*  https://github.com/caljrimmer/isomorphic-redux-app/blob/73e6e7d43ccd41e2eb557a70be79cebc494ee54b/src/common/api
*  /fetchComponentDataBeforeRender.js
*/
import Promise from 'bluebird';

export function fetchComponentData(dispatch, components, params) {
  const needs = components.reduce((prev, current) => {
    return (current.need || [])
      .concat((current.WrappedComponent ? current.WrappedComponent.need : []) || [])
      .concat(prev);
  }, []);
  const promises = needs.map(need => dispatch(need(params)));
  return Promise.all(promises);
}

It appears as though the “rootPostSaga” is not run at all when preparing the page to be rendered server-side.

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Comments:19 (13 by maintainers)

github_iconTop GitHub Comments

4reactions
yelouaficommented, May 5, 2016

As @ojkelly said you don’t need both fetchComponentData and the two-phase render on the server.

  1. If you know precisely your data dependencies for components on the server, then you don’t need to run the sagas on the server. Just fire your loading routine and when you get the data update the Store’s state and send the whole (rendered markup + initial state) to the client
  2. If you want to use universal Sagas, and assuming you’re using componentWillMount to dispatch data loading actions (componentDidMount will not work on the server) then you need to follow this steps:
    • Run the root saga of your app
    • RenderToString will trigger componentWillMount and thus will dispatch the load actions for Sagas. You need to close the store immediately after
    • root Saga will fork the necessary tasks and wait for them to finish. After all tasks finish (or after the 1st error). The done promise of the root saga will resolve/reject. At this point the Store’s state is already updated by Sagas and you can do your 2nd render that will be sent to the client

@ojkelly a small correction, the 2nd render is the one into done callback

2reactions
yelouaficommented, May 9, 2016

@ojkelly I think store.close() should come after the 1st renderToString so that components can dispatch their actions inside componentWillMount

Read more comments on GitHub >

github_iconTop Results From Across the Web

Challenges in server side rendering React apps (SSR)
Data Hydration is a typical problem we encounter with server side rendering. The server has pre-fetched data. However, when we call ReactDOM.
Read more >
What is server-side rendering: definition, benefits and risks
Server-Side Rendering (SSR) is an approach to rendering content for the website. ... There are fewer issues with social media indexing.
Read more >
Why Server-Side Rendering Alone Is Not the Solution
The problem is, because the server assembles the UI, the UI, the server, and the data(base) need to be near each other. Otherwise,...
Read more >
What is server-side rendering and how does it improve site ...
Server-side rendering ensures that website content appears quickly, without first having to download and run application code.
Read more >
The Challenges and Pitfalls of Server Side Rendering
Server - Side Rendering (SSR) has become a go-to method for improving the load time of dynamic websites. Rendering content on the server...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found