Next.js 의 SSR 에서 redux 사용하기 (next-redux-wrapper)
배경
Next.js 에서 SSR 서버사이드렌더링로 특정 페이지를 제대로 렌더링해서 클라이언트로 보내주고 싶었다.
따라서 아래 2가지를 구현해야 했다
- 서버사이드에서 액션을 dispatch 디스패치해서 데이터를 fetch하는 작업을 실행하기
- 새로워진 (데이터를 품은) state (store) 를 이용해서 유의미한 콘텐츠를 서버사이드에서 렌더링하기
해결 과정
next-redux-wrapper 로 getServerSideProps 를 감쌀 wrapper 만들기
- references
- my code
getServerSideProps 에서 store 이용하기
- references
- my code
데이터 fetch 가 완료될 때까지 기다리는 함수를 개인적으로 추가했다
type Options = {
actionType: DataSagaActionType
key: string
}
export const waitDuringLoading = async (store: Store<RootState, AnyAction>, {actionType, key}: Options) => {
while (true){
await (async () => new Promise(resolve => setTimeout(resolve, 100)))()
const isLoading = store.getState().data[actionType][key].status === DataSagaStatus.LOADING
if (!isLoading) break;
}
}
import {waitDuringLoading} from // ...
// ...
export const getServerSideProps = wrapper.getServerSideProps(store => async ({req, res, ...etc}) => {
const GET_PUBLIC_TASKS_KEY = ""
const GET_GOALS_BY_IDS_KEY = ""
// 액션 디스패치 하기
store.dispatch(dataActionCreators[DataActionType.GET_PUBLIC_TASKS]({
author: undefined,
key: GET_PUBLIC_TASKS_KEY,
startTime: new Date("1999-11-11"),
endTime: new Date("2222-11-11"),
}))
// 데이터 fetch 완료될때까지 기다리기
await waitDuringLoading(store, {actionType: DataActionType.GET_PUBLIC_TASKS, key: GET_PUBLIC_TASKS_KEY})
// state 에서 값 읽기
const tasksGoal = store.getState().data[DataActionType.GET_PUBLIC_TASKS][GET_PUBLIC_TASKS_KEY].data?.map(item => item.goal)
// ...
return ({
props: {}
})
});
클라이언트 state 와 서버 state 알맞게 합치기
- references
- my code
next-redux-wrapper 에서 작업해주는 HYDRATE 라는 액션을 이용해서
getStaticProps
나getServerSideProps
가 있는 페이지로 처음 접속 또는 이동할 때,- 이전 페이지에서의 클라이언트 측 state 와 서버 측에서 새로 만든 state 를 이용해서 최종 state 를 만든다
- 이때 merge 를 하는 방식이 생각보다 중요하다
- 나는 여러 state 를 묶어서 combinedReducer로 root state 를 만들어서 이용하는데, 여기서 일부 state (아래에서 navigation)는 client 측에서만 다루고 (즉 server 측에서는 이용하지 않고), 일부 state (아래에서 data)는 매번 deepmerge 로 서로 합치는 방식을 택했다
- deepmerge에서 배열을 합치는 방식도 정해야 한다. 나는 객체의 id 값을 이용해서 같은 객체로 취급할 것인지 정했다
import merge, {Options as MergeOptions} from "deepmerge"
import {HYDRATE} from "next-redux-wrapper";
import {combineReducers} from "redux";
import {dataReducer, State as DataState, initialState as dataInitialState} from "./data";
import {navigationReducer, State as NavigationState, initialState as navigationInitialState} from "./navigation";
const combinedReducer = combineReducers({
data: dataReducer,
navigation: navigationReducer,
});
export type RootState = {
data: DataState;
navigation: NavigationState;
}
const initialRootState: RootState = {
data: dataInitialState,
navigation: navigationInitialState
}
const arrayMerge: MergeOptions["arrayMerge"] = (previousArray, incomingArray, options) => {
const resultArray: typeof previousArray = [...previousArray]
incomingArray.forEach((incomingItem) => {
const prevItemIndex = previousArray.findIndex(previousItem => previousItem.id === incomingItem.id)
if (prevItemIndex !== -1){
resultArray[prevItemIndex] = merge(resultArray[prevItemIndex], incomingItem, options)
}
else {
resultArray.push(incomingItem)
}
})
return resultArray
}
const rootReducer = (previousClientState = initialRootState, action: any) => {
if (action.type === HYDRATE) {
const incomingServerState = action.payload as RootState
const nextState: RootState = {
navigation: previousClientState.navigation,
data: merge(previousClientState.data, incomingServerState.data, {arrayMerge})
}
return nextState;
} else {
return combinedReducer(previousClientState, action);
}
};
export default rootReducer;
렌더링이 잘 되서 클라이언트로 전달되는지 확인하기
- chrome network 탭에서 해당 요청 (페이지 path 이름) response 복사하기
- 복사한 hmtl 코드를 beautify (format)해서 body의 렌더링 부분 확인
- html 코드를 보기 좋게 나타내주는 웹사이트
Author And Source
이 문제에 관하여(Next.js 의 SSR 에서 redux 사용하기 (next-redux-wrapper)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@vltea/Next.js-의-SSR-에서-redux-사용하기-next-redux-wrapper저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)