Slog
Sign in

Next.js 의 서버사이드 렌더링에 Redux 적용하기

2021 Apr 30
3 min read
Next.js 의 서버사이드 렌더링에 Redux 적용하기

오늘은 Next.js의 SSR(서버사이드 렌더링) 에 next-redux-wrapper 를 이용하여 Redux를 적용하는 방법에 대해 알아보겠습니다.

개요

Next.js의 getInitialProps, getServerSideProps 등에서 store에 접근을 하기 위해 next-redux-wrapper 라는 라이브러리가 필요합니다.

또 부가적으로 Server Side 일 때의 Redux Store와 Client Side 일 때의 Redux Store를 합쳐주는 역할도 하기 때문에 Next.js에서 Redux를 적용하려면 해당 라이브러리가 필요합니다.

프로젝트에 적용하기

이제 next-redux-wrapper 를 프로젝트에 적용하는 방법을 알아보도록 하겠습니다.

패키지 설치

우선 패키지를 설치해줍니다.

yarn add next-redux-wrapper redux redux-devtools-extension

또는

npm i next-redux-wrapper redux redux-devtools-extension --save

폴더 구성

우선 src/stores/modules/counter.ts 를 생성해줍니다.

export const INCREMENT = "counter/INCREMENT" as const;
export const DECREMENT = "counter/DECREMENT" as const;

export const increment = () => ({
  type: INCREMENT
});

export const decrement = () => ({
  type: DECREMENT
});

export type CounterAction = ReturnType<typeof increment | typeof decrement>;

export interface ICounterState {
  count: number;
}

export const initialState: ICounterState = {
  count: 0
};

export default (state: ICounterState = initialState, action: CounterAction) => {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + 1 };
    case DECREMENT:
      return { count: state.count - 1 };
    default:
      return state;
  }
};

그 이후 RootReducer를 구성합니다.

src/stores/modules/index.ts

import { AnyAction, CombinedState, combineReducers } from "redux";
import { HYDRATE } from "next-redux-wrapper";
import counter, { ICounterState,  } from "./counter";

const rootReducer = (state: IState | undefined, action: AnyAction): CombinedState<IState> => {
  switch (action.type) {
    // 서버 사이드 데이터를 클라이언트 사이드 Store에 통합.
    case HYDRATE:
      return { ...action.payload }
    default: {
      const combineReducer = combineReducers({
        counter
      });
      return combineReducer(state, action);
    }
  }
};


export type RootState = ReturnType<typeof rootReducer>;
export default rootReducer;

interface IState {
  counter: ICounterState;
}

그 이후 마지막으로 src/stores/index.ts 를 구성하여 줍니다.

import { applyMiddleware, createStore, compose } from "redux";
import { createWrapper } from "next-redux-wrapper";
import { composeWithDevTools } from "redux-devtools-extension";
import reducer from "./modules";

const configureStore = () => {
  const middlewares = [YOUR_MIDDLEWARES];
  const enhancer =
    process.env.NODE_ENV === "production"
      ? compose(applyMiddleware(...middlewares))
      : composeWithDevTools(applyMiddleware(...middlewares));
  const store = createStore(reducer, enhancer);
  return store;
};

const wrapper = createWrapper(configureStore, {
  debug: process.env.NODE_ENV !== "production"
});

export default wrapper;

위 작업을 통하여 스토어 구성은 끝이 났습니다.

하지만 이렇게 할 시 계속해서 Server Side Store가 Client Side Store를 덮어 써버리는 일이 발생합니다.

이 일을 해결하려면 server 모듈을 생성하여 HYDRATE 에서 store를 덮어 썼다면 state로 저장하고, 만약 이미 덮어 쓴 state가 있으면 return { ...state }를 활용하여 더 이상 덮어 쓰지 않도록 구성하면 최초 1회HYDRATE 가 실행되는 효과를 볼 수 있습니다.

마지막으로 pages/_app.tsx 를 구성합니다.

import wrapper from "../stores";

... Skip

App.getInitialProps = async ({ Component, ctx }: AppContext): Promise<AppInitialProps> => {
  let pageProps = {};

  if (Component.getInitialProps) {
    pageProps = await Component.getInitialProps(ctx);
  }

  return { pageProps };
};

export default wrapper.withRedux(App);

그러면 이제 모든 Page 컴포넌트에서 getInitialProps(ctx) 의 ctx.store.dispatch 로 dispatch를 실행 할 수 있습니다. 또한 ctx.store.getState() 를 통하여 state도 가져올 수 있습니다.

마치며

이번엔 Next.js에서 Redux를 적용하는 방법에 대해 알아보았습니다. next-redux-wrapper 를 활용하면 서버사이드, 클라이언트사이드 모두 구성된다는 점이 편리한 것 같습니다.

추가적으로 궁금한 사항은 댓글로 남겨주시면 감사하겠습니다.

Explore Popular Contents

0 Comments

Anonymous