2

I’m struggling to get server-side rendering (SSR) to work with redux-api. The app works fine with just client-side rendering (CSR).

For SSR to work, I need the data to be available in Next.js’ getInitialProps function. I’m trying to use next-redux-wrapper to bind it together.

Current status:

class ShowLessonPage extends React.Component {

    static async getInitialProps ({store, isServer, pathname, query}) {
        console.log(`getInitialProps`, {store, isServer, pathname, query});
        const { dispatch } = store;
        const lessonSlug = query.lessonSlug;
        // Get one Lesson
        dispatch(reduxApi.actions.oneLesson({ id: `slug=${lessonSlug}` }));
    }

    render() {
        console.log('render', this.props.oneLesson);
        const lesson = this.props.oneLesson.data;
        //...
    }

    //.....

}

const createStoreWithThunkMiddleware = applyMiddleware(thunk)(createStore);
const reducer = combineReducers(myReduxApi.reducers); // redux-api

const makeStore = function (state, enhancer) {
    return createStoreWithThunkMiddleware(reducer, state);
}

const mapStateToProps = function (state) {
    return { oneLesson: state.oneLesson };
};

// withRedux = next-redux-wrapper
const ShowLessonPageConnected = withRedux({ createStore: makeStore, mapStateToProps: mapStateToProps })(ShowLessonPage)
export default ShowLessonPageConnected;

I at least get store into getInitialProps now, but I get a strange Error: only absolute urls are supported message that I didn’t have in my CSR (pre-withRedux) version of the app. And this.props.oneLesson.data is of course empty.

makeStore is getting a state=undefined on the server generated calls, maybe that’s a clue.

I’m also open to replacing redux-api with something else that works similarly.

UPDATE 1: by making all URLs full, Redux is now hitting my API endpoint. However, for 1 page reload it calls makeStore no less than 3 times, and only the first call contains the correct slug, see console output:

makeStore { state: undefined, reqParams: { lessonSlug: 'tyrannosaurus-rex' } }
getInitialProps { query: { lessonSlug: 'tyrannosaurus-rex' } }
API: GET request: { _id: 'slug=tyrannosaurus-rex' }
makeStore { state: undefined, reqParams: { lessonSlug: 'undefined' } }
getInitialProps { query: { lessonSlug: 'undefined' } }
API: GET request: { _id: 'slug=undefined' }
makeStore { state: undefined, reqParams: { lessonSlug: 'undefined' } }
getInitialProps { query: { lessonSlug: 'undefined' } }
API: GET request: { _id: 'slug=undefined' }

UPDATE 2: A breakthrough: returning a promise from getInitialProps makes SSR work. Now client-side rendering is acting up, funny enough.

static async getInitialProps ({store, isServer, pathname, query}) {
    const { dispatch } = store;
    const lessonSlug = query.lessonSlug;
    const resultPromise = await dispatch(reduxApi.actions.oneLesson({ id: `slug=${lessonSlug}` }));
    return resultPromise;
}
Tom Söderlund
  • 4,743
  • 4
  • 45
  • 67

1 Answers1

3

I managed to solve it, after a full day’s hair-pulling.

There was a number of issues before it worked as it should:

  1. Absolute URLs. The client-only code had no issue with relative URLs, but the server did.
  2. getInitialProps must return a Promise to the results (see below).
  3. My data object (oneLesson) ended up on this.state, not this.props as I expected (for my use case I don’t really care as long as I can render it both on server and client).

Here’s the final version of getInitialProps:

static async getInitialProps ({store, isServer, pathname, query}) {
    const { dispatch } = store;
    const lessonSlug = query.lessonSlug;
    const resultPromise = await dispatch(reduxApi.actions.oneLesson({ id: `slug=${lessonSlug}` }));
    return resultPromise;
}

Here’s a complete working example of Next.js, redux-api, and next-redux-wrapper working in harmony:

https://github.com/tomsoderlund/nextjs-express-mongoose-crudify-boilerplate

Tom Söderlund
  • 4,743
  • 4
  • 45
  • 67