Next.js 의 서버사이드 렌더링에 Redux 적용하기
3 min read
오늘은 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
를 활용하면 서버사이드, 클라이언트사이드 모두 구성된다는 점이 편리한 것 같습니다.
추가적으로 궁금한 사항은 댓글로 남겨주시면 감사하겠습니다.