https://github.com/seomimi/nextapp/tree/with_redux-tookit
이 글에서는 이 전에 작성했던 https://velog.io/@mimi0905/AWS-Amplify로-next-프로젝트-배포하기SSR
게시글의 코드를 가져와서 추가, 수정합니다.
리덕스 툴킷 패키지는 리덕스 논리를 작성하는 표준 방법으로, Redux 사용 시 생기는 아래와 같은 불편함을 해결하기 위해 만들어졌습니다.
리덕스 툴킷을 사용하면 리덕스 코드 개선에 도움을 주기 때문에 기존 애플리케이션 단순화에 기여할 수 있습니다.
@reduxjs/toolkit, react-redux, next-redux-wrapper 의 설치가 필요합니다.
설치한 모듈버전
"next": "^12.2.2",
"next-redux-wrapper": "^8.0.0",
"@reduxjs/toolkit": "^1.9.1"
Next.js에서 정적 앱을 만들거나 서버 측 렌더링을 하기 위해서는 서버에 다른 스토어 인스턴스가 필요합니다. 이 서버 스토어는 리덕스 스토어에 접근하여, 서버쪽 데이터와 리덕스의 데이터를 일치 시킬 수 있어야 합니다. 이 접근을 가능하게 해주는 것이 next-redux-wrapper입니다.
로그인 후 사용자의 아이디(이메일)을 저장하는 슬라이스 리듀서를 생성합니다.
// store/reducer/user.ts
import { createSlice } from '@reduxjs/toolkit';
interface userType {
email: string | null;
}
const initialState: userType = {
email: null,
};
export const slice = createSlice({
name: 'user',
initialState,
reducers: {
login: (state, action) => {
state.email = action.payload;
},
},
});
export const user = slice.name;
export const userReducer = slice.reducer;
export const userAction = slice.actions;
HYDRATE 상태를 정의하여야, 초기 렌더링 시 서버 측 데이터를 클라이언트에 저장하여 사용할 수 있습니다.
// store/index.ts
import { configureStore, combineReducers } from '@reduxjs/toolkit';
import { HYDRATE, createWrapper } from 'next-redux-wrapper';
import { userReducer } from './reducer/user';
const rootReducer = combineReducers({ user: userReducer });
export const makeStore = () => {
const store = configureStore({
reducer: (state, action) => {
switch (action.type) {
case HYDRATE:
return action.payload;
default:
return rootReducer(state, action);
}
},
devTools: process.env.NODE_ENV !== 'production',
});
return store;
};
const wrapper = createWrapper(makeStore, { debug: process.env.NODE_ENV === 'development' });
export type RootState = ReturnType<typeof rootReducer>;
export default wrapper;
이 때, 설치한 next-redux-wrapper 버전에 따라 코드가 약간 다릅니다.
useWrappedStore()를 사용하여 store를 제공합니다.
// pages/_app.tsx
import wrapper from '../store';
import { Provider } from 'react-redux';
function MyApp({ Component, pageProps }: AppProps) {
const { store, props } = wrapper.useWrappedStore(pageProps);
return (
<Provider store={store}>
<Component {...props} />
</Provider>
);
}
export default MyApp;
withRedux로 앱을 감싸서 store 제공합니다.
MyApp을 함수형이 아닌 클래스형으로 작성했을 때에도 아래와 같은 방법을 사용합니다.
import wrapper from '../store';
// 함수형
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
/*
// 클래스형
class MyApp extends React.Component<AppProps> {
render() {
const {Component, pageProps} = this.props;
return <Component {...pageProps} />;
}
}
*/
export default wrapper.withRedux(MyApp);
// pages/index.tsx
import type { NextPage, GetServerSideProps } from 'next';
import wrapper, { RootState } from '../store';
import { userAction } from '../store/reducer/user';
import { useSelector, useDispatch } from 'react-redux';
export const getServerSideProps: GetServerSideProps = wrapper.getServerSideProps((store) => async (context) => {
const { Auth } = withSSRContext(context);
const user = await Auth.currentUserInfo()
.then((res: any) => {
store.dispatch(userAction.login(res.attributes.email || null));
// 로그인 된 상태일 떄, 스토어에 email을 저장
return res?.attributes.email || null;
})
.catch((error: Error) => {
console.log(error);
return null;
});
return { props: { } };
});
만약에 리덕스 스토어 생성시 HYDRATE상태를 정의하지 않으면
서버 측 스토어에는 아래와 같이 데이터가 저장되어 있지만,
클라이언트 측 스토어에는 데이터가 저장되어 있지 않습니다.