Next.js + Redux로 확장 가능한 모달 시스템 만들기 (with App Router)

2025. 5. 6. 21:20·Next

☑️ 들어가며

지금 작업 중인 Next.js App Router 기반 프로젝트에서, 여러 개의 모달(지출/수입 등록, 삭제 확인, 얼럿 등)을 확장 가능하게 만들고 싶어서 Redux와 type 기반 상태 관리를 활용한 범용 모달 시스템을 만들었다.

이 글에서는 그 구조와 실제 구현 과정을 정리하려고 한다.


☑️ 모달 구조 설계

⭐ 구조 목표

  1. 한 개의 CommonModal 컴포넌트로 다양한 모달 대응
  2. Redux 상태에서 모달의 type을 기준으로 열고 닫기
  3. modalProps 확장 여지도 확보

1️⃣ modalSlice.ts 생성

// features/modal/modalSlice.ts

import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  type: null, // 'expense', 'delete', 'alert' 등
};

const modalSlice = createSlice({
  name: "modal",
  initialState,
  reducers: {
    openModal: (state, action) => {
      state.type = action.payload.type;
    },
    closeModal: () => initialState,
  },
});

export const { openModal, closeModal } = modalSlice.actions;
export default modalSlice.reducer;
😭 문제1. Redux 상태인데 모든 모달이 동시에 열리는 문제

처음에 modal.isOpen: boolean 으로만 모달 상태를 관리했더니, 여러 모달이 동시에 열리는 상황이 발생했다.
각 모달을 구분하지 않으니 조건 분기가 불가능한거다. (당연한 소리...ㅎㅎ)

🥳 해결
modal.type을 상태로 관리해서 모달의 "종류"를 기준으로 열고 닫도록 변경!

2️⃣ 모달 훅 작성

// hooks/useModal.ts

import { useDispatch } from "react-redux";
import { openModal, closeModal } from "@/features/modal/modalSlice";

export const useModal = () => {
  const dispatch = useDispatch();

  const handleOpenModal = ({ type }: { type: string }) => {
    dispatch(openModal({ type }));
  };

  const handleCloseModal = () => {
    dispatch(closeModal());
  };

  return { openModal: handleOpenModal, closeModal: handleCloseModal };
};

 

3️⃣ CommonModal 컴포넌트 작업

// components/modal/CommonModal.tsx

"use client";

import { ReactNode, useEffect } from "react";

type CommonModalProps = {
  isOpen: boolean;
  onClose: () => void;
  children: ReactNode;
};

export default function CommonModal({ isOpen, onClose, children }: CommonModalProps) {
  useEffect(() => {
    const handleEsc = (e: KeyboardEvent) => {
      if (e.key === "Escape") onClose();
    };
    document.addEventListener("keydown", handleEsc);
    return () => document.removeEventListener("keydown", handleEsc);
  }, [onClose]);

  if (!isOpen) return null;

  return (
    <div onClick={onClose}>
      <div onClick={(e) => e.stopPropagation()}>
        <button onClick={onClose}>닫기</button>
        {children}
      </div>
    </div>
  );
}

 

4️⃣ 사용 예시(테스트): DashboardLayout.tsx

"use client";

import { useModal } from "@/hooks/useModal";
import { useSelector } from "react-redux";
import { RootState } from "@/store";
import CommonModal from "@/components/modal/CommonModal";

export default function DashboardLayout() {
  const { openModal, closeModal } = useModal();
  const modalType = useSelector((state: RootState) => state.modal.type);

  return (
    <>
      <button onClick={() => openModal({ type: "expense" })}>모달 열기</button>

      <CommonModal isOpen={modalType === "expense"} onClose={closeModal}>
        <p>지출 등록 폼 들어갈 자리!</p>
      </CommonModal>
    </>
  );
}

😭 문제2. Redux Provider를 layout.tsx에서 사용해서 에러

Next.js의 layout.tsx는 기본적으로 Server Component인데, 여기서 클라이언트 전용인 <Provider>를 사용해서 에러 발생. (사실 제일 기본인데, 이걸 또 놓침)

🥳 해결
ReduxProvider라는 클라이언트 전용 래퍼 컴포넌트를 만들어 분리!
// components/providers/ReduxProvider.tsx
"use client";
export default function ReduxProvider({ children }) {
  return <Provider store={store}>{children}</Provider>;
}

// layout.tsx에서는 사용만
<ReduxProvider>{children}</ReduxProvider>

✨ 배운점

모달은 UI에서 자주 쓰이는 컴포넌트지만 Next.js App Router + Redux + SCSS 환경에서는 작은 실수들이 에러로 이어질 수 있다는 걸 알았다.

 

이번 경험을 통해 단순히 모달을 만드는 것을 넘어서, 서버 컴포넌트 vs 클라이언트 컴포넌트 개념, Redux 구조화에 깊이 이해할 수 있었다.

'Next' 카테고리의 다른 글

Next 기반 학원비 통계 컴포넌트 개발 회고  (0) 2025.07.10
Firebase를 활용한 로그인 & 회원가입 구현하기 (feat. React Hook Form)  (0) 2025.05.13
[Next] 페이지별 레이아웃 설정하기  (1) 2024.11.18
[Next] API Routes  (3) 2024.11.15
[Next] 프리페칭(Pre-fetching)  (0) 2024.11.07
'Next' 카테고리의 다른 글
  • Next 기반 학원비 통계 컴포넌트 개발 회고
  • Firebase를 활용한 로그인 & 회원가입 구현하기 (feat. React Hook Form)
  • [Next] 페이지별 레이아웃 설정하기
  • [Next] API Routes
FE Dev. 굼지
FE Dev. 굼지
굼지의 웹 개발 레시피 입니다.
  • FE Dev. 굼지
    굼지의 웹 개발 레시피
    FE Dev. 굼지
    • 분류 전체보기 (14)
      • FrontEnd (3)
      • React (3)
      • Next (8)
      • React-Native (0)
  • 블로그 메뉴

    • 홈
    • 태그
  • 최근 글

FE Dev. 굼지
Next.js + Redux로 확장 가능한 모달 시스템 만들기 (with App Router)
상단으로

티스토리툴바