Next.js: Server side rendered page displays for a second and then page re renders on client side and state seems to be empty

Created on 23 Nov 2017  路  21Comments  路  Source: vercel/next.js


I am using Redux-saga to manage my states and to manage my API calls. My page renders properly on Server side and correct html is sent to client.
The issue here is on client side my page renders again and the state on client side seems to be different from that of server.

In my project i have multiple containers and components and i am combining my reducers and sagas.
The Code Is as Follows:

Store

import { createStore, applyMiddleware, compose } from 'redux';
import withRedux from 'next-redux-wrapper';
import thunkMiddleware from 'redux-thunk';
import { fromJS } from 'immutable';

import nextReduxSaga from 'next-redux-saga';
import createSagaMiddleware from 'redux-saga';
import mainSagas from './sagas';
import reducer, { initialState } from './reducers';

const sagaMiddleware = createSagaMiddleware()

export function configureStore (state = initialState) {
    const middlewares = [
      sagaMiddleware,
      thunkMiddleware,
    ];
    const store = createStore(
      reducer,
      fromJS(state),
      compose(applyMiddleware(...middlewares)),
    );

    store.sagaTask = sagaMiddleware.run(mainSagas);
    return store
}

export function withReduxSaga (BaseComponent) {
  return withRedux(configureStore)(nextReduxSaga(BaseComponent))
}

Reducers.js

import { combineReducers } from 'redux'
import homeReducer, { initialState as homeState } from 'containers/Home/reducer';
import rootReducer, { initialState as rootState } from 'containers/Root/reducer';
import aboutUsReducer, { initialState as aboutUsState } from 'containers/AboutUs/reducer';
import contactUsReducer, { initialState as contactUsState } from 'containers/ContactUs/reducer';
import portfoliosReducer, { initialState as portfoliosState } from 'containers/Portfolios/reducer';
import portfolioReducer, { initialState as portfolioState } from 'containers/Portfolio/reducer';
import { fromJS } from 'immutable';

export const intitialState = fromJS({
  home: homeState,
  root: rootState,
  aboutUs: aboutUsState,
  contactUs: contactUsState,
  portfolios: portfoliosState,
  portfolio: portfolioState
});

export default combineReducers({
  home: homeReducer,
  root: rootReducer,
  aboutUs: aboutUsReducer,
  contactUs: contactUsReducer,
  portfolios: portfoliosReducer,
  portfolio: portfolioReducer,
})

Sagas.js

import { fork } from 'redux-saga/effects'
import homeSagas from 'containers/Home/sagas';
import rootSagas from 'containers/Root/sagas';
import aboutUsSagas from 'containers/AboutUs/sagas';
import contactUsSagas from 'containers/ContactUs/sagas';
import portfoliosSagas from 'containers/Portfolios/sagas';
import portfolioSagas from 'containers/Portfolio/sagas';

export default function* mainSagas() {
    yield [
      fork(homeSagas),
      fork(rootSagas),
      fork(aboutUsSagas),
      fork(contactUsSagas),
      fork(portfoliosSagas),
      fork(portfolioSagas),
    ]
  }

index.js (Landing Page)
In the Saga i simply make the API call and set the data in the state. It is working fine

import React from 'react';
import HomeContainer from 'containers/Home';
import { withReduxSaga } from 'store'
import {
  getSettings,
  getAboutUs,
  getTeam,
  getServices,
  getPortfolio,
  getPortfolioSettings,
  getTestimonials
} from 'containers/Home/actions';

class Home extends React.Component {

  static async getInitialProps({ store }) {
    await store.dispatch(getSettings())
    await store.dispatch(getAboutUs())
    await store.dispatch(getTeam())
    await store.dispatch(getServices())
    await store.dispatch(getPortfolio())
    await store.dispatch(getPortfolioSettings())
    await store.dispatch(getTestimonials())
  }

    render() {
        return (
      <HomeContainer />
        );
    }
}

export default withReduxSaga(Home)

I am using 'Reselect' to select the data from state.
Selector.js

import { createSelector } from 'reselect';
const selectHomeState = () => (state) => state.home;

const selectSettings = () => createSelector(
  selectHomeState(),
  (homeState) => homeState.get('Settings')
);

const selectGetSettingsStatus = () => createSelector(
  selectHomeState(),
  (homeState) => homeState.get('getSettingsStatus')
);

const selectAboutUs = () => createSelector(
  selectHomeState(),
  (homeState) => homeState.get('AboutUs')
);

const selectGetAboutUsStatus = () => createSelector(
  selectHomeState(),
  (homeState) => homeState.get('getAboutUsStatus')
);

const selectTeam = () => createSelector(
  selectHomeState(),
  (homeState) => homeState.get('Team')
);

const selectGetTeamStatus = () => createSelector(
  selectHomeState(),
  (homeState) => homeState.get('getTeamStatus')
);

const selectServices = () => createSelector(
  selectHomeState(),
  (homeState) => homeState.get('Services')
);

const selectGetServiceStatus = () => createSelector(
  selectHomeState(),
  (homeState) => homeState.get('getServicesStatus')
);

const selectTestimonials = () => createSelector(
  selectHomeState(),
  (homeState) => homeState.get('Testimonials')
);

const selectGetTestimonialsStatus = () => createSelector(
  selectHomeState(),
  (homeState) => homeState.get('getTestimonialsStatus')
);

const selectPortfolios = () => createSelector(
  selectHomeState(),
  (homeState) => homeState.get('Portfolios')
);

const selectGetPortfoliosStatus = () => createSelector(
  selectHomeState(),
  (homeState) => homeState.get('getPortfoliosStatus')
);

const selectPortfolioSettings = () => createSelector(
  selectHomeState(),
  (homeState) => homeState.get('PortfolioSettings')
);

const selectGetPortfolioSettingsStatus = () => createSelector(
  selectHomeState(),
  (homeState) => homeState.get('getPortfolioSettingsStatus')
);

export {
  selectSettings,
  selectGetSettingsStatus,
  selectAboutUs,
  selectGetAboutUsStatus,
  selectTeam,
  selectGetTeamStatus,
  selectServices,
  selectGetServiceStatus,
  selectTestimonials,
  selectGetTestimonialsStatus,
  selectPortfolios,
  selectGetPortfoliosStatus,
  selectPortfolioSettings,
  selectGetPortfolioSettingsStatus
};

When i reload the page my server side rendered page is displayed and then with a flicker all data is lost. And page without data is displayed. When i do view page source i can see the correct html sent from the server.

I could not figure out the issue. Any help in this regard will be appreciated.

  • [x] I have searched the issues of this repository and believe that this is not a duplicate.

Expected Behavior


Current Behavior


Steps to Reproduce (for bugs)



1.
2.
3.
4.

Context


Your Environment


| Tech | Version |
|---------|---------|
| next | 4.1.4 |
| node | >=6 |
| OS | win 10 |
| browser | chrome |
| etc | |

All 21 comments

Are you using the same store for the server and browser ? You should always use a new store for the SSR and a new but unique store for the browser.

If you're getting the initialState in the SSR then you need to send it to the client somehow (using the returned props in getInitialProps is an easy way) or create a new initialState on the client again because the store in the client will always be different.

I am using the same store at both ends. Can you please give some example where we pass our store initialized in server to the client.

take a look to the examples in next with redux

If you look in my index.js I am already setting the initialstate in getInitialProps method.
How can I pass the latest updated state from server to the client side. Can you please show any example..?

I have already been through both the examples the redux and with-redux-saga one. I can't figure out what am I missing. I would really appreciate if you can point out the issue.

I am using combineReducers. I think there is some issue in multiple reducers case.

I really can't see where the issue is, I just wanted to make you realize something that you might had forgotten, something similar happened to me in the past but I'm not using redux saga or Reselect, only Redux and Apollo

If you can't see the initialState in the browser do a console.log before setting the initialState and you should see the same state logged in the terminal and in the browser

I have tried consoling the settings field from state. But I am getting undefined in browser console. But data come in terminal.

That's the issue which I am facing. But can't figure out what is the issue. And due to this server side rendered page disappears and empty page comes after a flicker.

is the creator of your initialState getting called in the browser too ? (getInitialProps will only be called once in the first render for SSR but not for the browser)

Yeah it's just called once. I had understanding that it will be created once and the same will be used in both server and client

So what do you suggest for the browser. Do I need to create a new store. If yes then will it contain the same state as server..?

you shouldn't according to this: https://github.com/kirill-konshin/next-redux-wrapper#how-it-works.

they will create a new store for the client and send you the initialState created server side and is your duty to apply that state to the new store

This seems to be helpful. I'll try this and will comment if it fixes my problem.

This seems to be helpful. I'll try this and will comment if it fixes my problem.

@WaqasArshad777 did you figure out what's wrong?

Tried a lot but couldn't figure out. Do share if you find any solution.

the problem is Saga not 'run' in server,
steps

  1. First, You need to activate saga tasks in server
  2. You need to dispatch action / saga action in getInitialProps
    for example, is the action is 'async' like fetch data to api server,
    then 3. you need to waiting for this process to be done
    see https://github.com/zalabinc/nextjs-boilerpate for example implementation
    https://github.com/zalabinc/nextjs-boilerpate/blob/master/app/containers/HomePage/index.js
    https://github.com/zalabinc/nextjs-boilerpate/blob/master/app/utils/monitorSagas.js

Replace fromJS(state) in the Store to state and then add fromJS directly to your reducers. Immutable in the NextJS is a pain.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

poyiding picture poyiding  路  73Comments

ematipico picture ematipico  路  66Comments

acanimal picture acanimal  路  74Comments

matthewmueller picture matthewmueller  路  102Comments

rauchg picture rauchg  路  208Comments