Nextjs Redux next-redux-wrapper

OkGyoung·2023년 8월 11일
0

2023.11 이전 자료

목록 보기
12/30

몇일 동안 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 컴포넌트 활용)에서 로그인 로그아웃 여부를 확인해서 레더링했다.

아직 확실하게 이해하지 못한 부분은 많지만 더욱 개발하면서 익혀야겠다.

profile
이유를 생각하는 개발자

1개의 댓글

comment-user-thumbnail
2023년 8월 11일

잘 읽었습니다. 좋은 정보 감사드립니다.

답글 달기