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

1년 전
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 를 활용하면 서버사이드, 클라이언트사이드 모두 구성된다는 점이 편리한 것 같습니다.

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

댓글 1
권용빈
권용빈1년 전
멋있어요 👍👍