몇일 동안 Nextjs, Redux-toolkit을 이용한 사이트 제작을 공부 하면서 새로이 알게 된 점과 phryneas/ssr-experiments의 예를 통해 배운점을 정리한다.
먼저 계속 씨름 했던 부분은 Nextjs에서 서버사이드로 Redux를 사용하려 했던점이다. Nextjs의 핵심 중 하나인 SSR, SSG를 이용해 서버쪽에서 미리 렌더링 할때 token을 이용해서 로그인 처리를 하려했다
주요 과정은
refresh토큰 확인
ㄴYes => redux user state access토큰 확인
ㄴYes => 저장된 access로 api활용(redux-persist 세션 저장)
ㄴNo => refresh토큰으로 access토큰을 받아와 redux저장
ㄴNo => 비 로그인화면 출력
백엔드는 DRF로 이메일 로그인, 소셜로그인 구성을 한 상태였고 nextjs 13에서 react server component이용해 구현하려 했지만 쉽지 않았다.
그렇게 검색을 하다보니 next-redux-wrapper를 개발한 개발자도 13버젼의 AppRouter 방식에서 사용은 아직 힘들다고 전했다. 그래서 page방식으로 전환 후 다시 진행 중 기존 사용하던 방식에서 next-redux-wrapper와 PageRouter 방식으로 전환하며 https://github.com/phryneas/ssr-experiments 의 예를 보고 많은 도움을 받았다.
가장 중요하 SSR에서 데이터를 받는 방식이다. 아래 3부분을 통해서 SSR에서 데이터를 미리 받을 수 있도록 한다.
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { HYDRATE } from "next-redux-wrapper";
export const pokemonApi = createApi({
baseQuery: fetchBaseQuery({
baseUrl: "https://pokeapi.co/api/v2/",
}),
extractRehydrationInfo(action, { reducerPath }) {
if (action.type === HYDRATE) {
return action.payload[reducerPath];
}
},
tagTypes: [],
endpoints: (builder) => ({
getPokemonByName: builder.query<
{ species: { name: string }; sprites: { front_shiny: string } },
string
>({
query: (name) => `pokemon/${name}`,
}),
getPokemonList: builder.query<{ results: Array<{ name: string }> }, void>({
query: () => `pokemon/`,
}),
}),
});
// Export hooks for usage in functional components
export const {
useGetPokemonByNameQuery,
useGetPokemonListQuery,
util: { getRunningQueriesThunk },
} = pokemonApi;
// export endpoints for use in SSR
export const { getPokemonByName, getPokemonList } = pokemonApi.endpoints;
위 코드에서 주목할 점은 아래 3부분이다.
extractRehydrationInfo(action, { reducerPath }) {
if (action.type === HYDRATE) {
return action.payload[reducerPath];
}
},
export const {
useGetPokemonByNameQuery,
useGetPokemonListQuery,
util: { getRunningQueriesThunk },
} = pokemonApi;
export const { getPokemonByName, getPokemonList } = pokemonApi.endpoints;
물론 Queries를 이용한 방식은 위처럼 이용하면 된다.
하지만 Mutation을 통해서 서버에 요청을 보내는 과정은(redux-persist 세션에 access토큰이 저장되지 않았을 때 실행)달랐다.
일딴 내 생각은 서버에서 미리 확인해서 로그인 혹은 로그아웃 과정을 보여주어서 렌더링을 하는 것이였지만 생각해보면 세션에 저장된 값을 서버에서 미리 가져와서 하려면 redux-persist-cookie-storage통해 쿠키를 통한 전달로 가능하긴 하지만 보안측면에서 좋지 못하다고 생각했다.
그래서 refresh토큰으로 여부를 확인하고 저장하는 과정은 클라이언트 측에서 실행하고 세션에 저장하기로 했다.
//store.ts
import { configureStore, combineReducers } from "@reduxjs/toolkit";
import { createWrapper } from "next-redux-wrapper";
import { foodApi } from "./services/foodApi";
import dietReducer from "./features/dietSlice";
import dishReducer from "./features/dishSlice";
import foodReducer from "./features/foodSlice";
import userReducer from "./features/userSlice";
import { dietApi } from "./services/dietApi";
import { dishApi } from "./services/dishApi";
import { authApi } from "./services/authApi";
import sessionStorage from "redux-persist/lib/storage/session";
import {
persistReducer,
persistStore,
FLUSH,
REHYDRATE,
PAUSE,
PERSIST,
PURGE,
REGISTER,
} from "redux-persist";
const persistConfig = {
key: "food",
version: 1,
storage: sessionStorage,
};
const rootReducer = combineReducers({
dishReducer,
dietReducer,
foodReducer,
userReducer,
[foodApi.reducerPath]: foodApi.reducer,
[dietApi.reducerPath]: dietApi.reducer,
[dishApi.reducerPath]: dishApi.reducer,
[authApi.reducerPath]: authApi.reducer,
});
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const makeStore = () =>
configureStore({
reducer: persistedReducer,
devTools: process.env.NODE_ENV !== "production",
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
},
}).concat([
foodApi.middleware,
dietApi.middleware,
dishApi.middleware,
authApi.middleware,
]),
});
export type AppStore = ReturnType<typeof makeStore>;
export type RootState = ReturnType<AppStore["getState"]>;
export type AppDispatch = AppStore["dispatch"];
export const store = makeStore();
export const persistor = persistStore(store);
export const wrapper = createWrapper<AppStore>(makeStore, {
debug: process.env.NODE_ENV === "development",
});
위처럼 설정한 후 클라이언트측(Navbar 컴포넌트 활용)에서 로그인 로그아웃 여부를 확인해서 레더링했다.
아직 확실하게 이해하지 못한 부분은 많지만 더욱 개발하면서 익혀야겠다.
잘 읽었습니다. 좋은 정보 감사드립니다.