Skip to content

Hydration Never Occurs When Using getStaticProps and fallback:true #570

@gabriel-earle

Description

@gabriel-earle

Describe the bug

In my implementation, the HYDRATE action is never dispatched when using getStaticProps and fallback: true on an initial page load. I've added detailed logs to my HYDRATE action handler, and I've found that the logs come out in the node console that is running the local next server and the resulting state looks perfect. However, these logs never appear in the browser console. Additionally, using Redux Devtools, the HYDRATE action type is never observed.

This bug only occurs on initial page load. If I start on a different page, then navigate to the getStaticProps/fallback: true page through a nextjs component, then everything works perfectly, and I observe the HYDRATE action type in Redux Devtools. Also, If I switch the loading behavior to fallback: "blocking" in getStaticPaths, the HYDRATE action is dispatched as expected, and everything works perfectly, however, I need support for fallback: true for my use case. Even more strange, if I use fallback: true, load the page, then subsequently save my code to trigger a hot reload, then the HYDRATE action is dispatched.

I am using redux toolkit, specifically using createSlice and configureStore to setup redux, but not using any of the API features.

My code

I've removed variable names/repetition for brevity/privacy, but all of the information is here.

My store definition:

const makeStore = () => {
  return configureStore({
    reducer: {
      // 15 slices of the following pattern:
      [X.reducerPath]: X.reducer
    },
  });
};
export const wrapper = createWrapper(makeStore);

my _app.js file (irrelevant parts removed). As you can see, it passes the store returned from wrapper.useWrappedStore to the Provider, and it also places the store in the api class, which I know is an unconventional behavior, however when removing this line, it does not fix the bug.

export default function App({ Component, ...rest }) {
  const { store, props } = wrapper.useWrappedStore(rest);
  api.store = store;

  useEffect(() => {
    // init api
    api.init();
  }, []);

  return (
      <main className={font.className}>
        <Provider store={store}>
          <Component {...props.pageProps} />
        </Provider/>
      </main>
  )
}

my getStaticProps usage:

export const getStaticProps = wrapper.getStaticProps(
  (store) =>
    async ({ params }) => {
      try {
        if (params.id) {
          const response = await axios.get(`/endpoint/${params.id}`);
          if (response.data.comments) {
            store.dispatch({
              type: "comments/storeMany",
              payload: response.data.comments,
            });
          }
        }
      } catch (err) {}

      return { props: {}, revalidate: 600 };
    }
);

my HYDRATE action hander. I'm doing it in the extraReducers section of redux toolkit's createSlice for each slice to keep the code separate. This is definitely working, because when logging through the local next server, the correct new state is returned for every slice. And, when switching to fallback: "blocking" for testing, the hydration result is perfect. So this shouldn't be the issue.

extraReducers: (builder) => {
      builder.addCase(HYDRATE, (state, action) => {
        const newEntries = action.payload[name].cache.filter(Boolean);
        if (!newEntries.length) return state;
        return { ...state, cache: mapConcat(state.cache, newEntries) };
      });
    },

What can I do to make sure the HYDRATE action is dispatched on the client after first navigations to a page using fallback: true and getStaticProps?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions