Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags more
Archives
Today
Total
관리 메뉴

나 개발자 할래요

비회원과 회원 사용자의 폼 데이터 매끄럽고 안전하게 저장하는 방법 - 세션스토리지와 Supabase 활용 본문

개발자 되는 법.../TIL...

비회원과 회원 사용자의 폼 데이터 매끄럽고 안전하게 저장하는 방법 - 세션스토리지와 Supabase 활용

개발_자 2024. 11. 20. 14:44

청첩장 제작 웹 어플리케이션은 회원과 비회원 사용자 모두에게 매끄러운 사용자 경험을 제공하는 것이 핵심입니다. 특히, 입력한 데이터가 예상치 못하게 사라지거나 저장되지 않는다면 사용자는 큰 불편을 겪게 됩니다.

이번 글에서는 폼 데이터를 안전하게 관리하고 데이터 유실을 방지하기 위한 요구사항들을 해결하기 위해 Supabase와 세션스토리지(SessionStorage)를 활용하여 비회원 및 회원 데이터를 효과적으로 처리한 방법에 대해 다뤄볼 것입니다

 


🚩 요구사항 1: 비회원 사용자 데이터를 안전하게 저장하라!

청첩장 서비스를 이용하는 사용자 중 상당수는 비회원으로, 데이터를 임시로 저장했다가 나중에 회원가입하는 경우가 많습니다. 이런 상황에서

  • 브라우저를 닫더라도 데이터가 유지되어야 한다.
  • 비회원이 회원가입하면 기존 데이터를 안전하게 전송해야 한다.

해결방법

비회원 데이터는 브라우저의 세션스토리지(SessionStorage)에 임시로 저장하도록 설정했습니다. 세션스토리지를 선택한 이유는 다음과 같습니다.

 

1. 비회원 처리에 적합한 데이터 유지 기간

세션스토리지(SessionStorage)는 로컬스토리지(LocalStorage)와 달리, 브라우저 세션 동안만 데이터가 유지됩니다.

  • 탭을 닫으면 데이터가 자동으로 삭제되므로, 비회원 데이터 관리에 적합합니다.
  • 비회원 사용자는 대부분 일회성 작업(청첩장 작성 후 회원가입)을 수행하므로, 세션스토리지의 특성이 요구사항에 잘 부합합니다.

2. 보안성과 개인정보 보호

  • 로컬스토리지(LocalStorage)는 데이터를 장기적으로 저장하지만, 노출 위험이 높아 보안에 취약할 수 있습니다.
  • 세션스토리지는 브라우저를 닫으면 데이터가 삭제되므로, 개인정보 보호보안성 측면에서 더 유리합니다.

구현흐름

  • 비회원 데이터 저장: 사용자가 폼 데이터를 입력할 때마다 세션스토리지에 실시간 저장.
  • 회원가입 이후 처리:
    • 회원가입 시 세션스토리지의 데이터를 Supabase로 전송하여 저장.
    • 데이터 전송 완료 후 세션스토리지 초기화로 데이터 중복 방지.
// 세션스토리지에 데이터 저장
sessionStorage.setItem('invitationFormData', JSON.stringify(formData));

// 회원가입 후 Supabase로 데이터 전송
export const saveSessionDataToSupabase = async (client: SupabaseClient, userId: string) => {
  const sessionData = sessionStorage.getItem('invitationFormData');

  if (!sessionData) return;

  const parsedData = JSON.parse(sessionData);
  const convertedData = {
    ...convertToSnakeCase(parsedData),
    user_id: userId,
  };

  const { data: existingData } = await client.from('invitation').select('user_id').eq('user_id', userId).maybeSingle();

  if (!existingData) {
    const { error } = await client.from('invitation').insert(convertedData);

    if (error) {
      console.error(error);
    }
  }
  sessionStorage.removeItem('invitationFormData');
};
 

결과

  • 브라우저를 새로고침하거나 닫아도 데이터 손실 방지
  • 비회원 데이터가 브라우저를 종료하면 자동으로 삭제되어 유출 위험 감소.
  • 비회원 데이터가 Supabase로 안전하게 전송되어 회원가입 후에도 작업을 이어나갈 수 있음.

🚩 요구사항 2: 데이터를 잃어버리지 않게 자주 저장하라!

폼 작성 중 사용자가

  • 다른 페이지로 이동하거나
  • 의도치 않게 입력을 멈추는 상황에서도 데이터를 잃지 않도록 자동 저장이 필요했습니다.

문제점과 개선

초기에는 setInterval로 일정 시간마다 데이터를 저장했으나, 데이터 변경 여부를 무조건 확인하면서 불필요한 API 호출이 많아져 성능 저하 문제가 있었습니다. 이를 해결하기 위해 디바운싱(debouncing)을 적용했습니다.

 

개선 전 코드 (setInterval 사용)

const AUTO_SAVE_INTERVAL: number = 120000;
const [prevFormData, setPrevFormData] = useState(methods.getValues());

const interval = setInterval(async () => {
  const { data: user } = await browserClient.auth.getUser();
  const formData = methods.getValues();
 
  const isInvitationModified = JSON.stringify(formData) !== JSON.stringify(prevFormData);

  if (isInvitationModified) {
    if (!user.user) {
      sessionStorage.setItem('invitationFormData', JSON.stringify(formData)); // 비회원은 세션스토리지에 저장
    } else {
      if (existingInvitation === null) {
        insertInvitation(formData); // 회원은 Supabase에 삽입
      } else {
        updateInvitation(formData); // 기존 데이터가 있으면 업데이트
      }
    }

    setPrevFormData(formData);
  }
}, AUTO_SAVE_INTERVAL);

return () => clearInterval(interval);
}, [existingInvitation, methods]);

 

디바운싱을 통한 최적화

폼 데이터가 변경되면 일정 시간 후에만 저장 작업을 수행하도록 디바운싱을 구현했습니다. 이렇게 하면 불필요한 API 호출을 줄이고, 필요한 시점에만 데이터를 저장할 수 있습니다.

 

개선된 코드 (디바운싱 적용)

const SAVE_DELAY_TIME: number = 3000;
const prevFormDataRef = useRef(""); // 이전 폼 데이터를 저장
 

  // 디바운싱된 저장 함수
  const handleDebouncedSave = debounce(async () => {
    const { data: user } = await browserClient.auth.getUser();
    const formData = methods.getValues();

    const isInvitationModified = JSON.stringify(formData) !== prevFormDataRef.current;

    if (isInvitationModified) {
      if (!user.user) {
        sessionStorage.setItem('invitationFormData', JSON.stringify(formData)); // 비회원일 경우 세션스토리지에 저장
      } else {
        if (existingInvitation === null) {
          insertInvitation(formData); // 회원일 경우 Supabase에 데이터 삽입
        } else {
          updateInvitation(formData); // 기존 데이터가 있으면 업데이트
        }
      }
      prevFormDataRef.current = JSON.stringify(formData); // 이전 데이터를 현재로 업데이트
    }
  }, SAVE_DELAY_TIME); // 일정 시간 이후 저장


  // 폼 값 변화 감지 후 디바운싱 함수 실행
  const subscribeEveryValues = () => {
    const subscription = methods.watch((value) => {
      if (value) {
        handleDebouncedSave(); // 값이 변경될 때마다 디바운싱 함수 호출
      }
      return () => subscription.unsubscribe(); // 구독 해제
    });
  };


  // useEffect 훅을 사용하여 컴포넌트가 마운트될 때 구독 시작
  useEffect(() => {
    subscribeEveryValues();
  }, [methods]); // methods가 변경될 때마다 useEffect가 실행됨

 

개선 후 동작

  • prevFormDataRef: useRef를 사용하여 이전 데이터를 저장하고, 데이터 변경 여부를 확인.
  • handleDebouncedSave: 디바운싱된 저장 함수로, 데이터가 변경될 때만 저장을 실행합니다. 비회원은 세션스토리지에, 회원은 Supabase에 데이터를 저장.
  • methods.watch: 폼 값이 변경될 때마다 handleDebouncedSave를 호출하여 디바운싱된 저장을 트리거.

 

결과

  • 저장 호출 횟수가 줄어들어 성능 최적화.
  • 폼 데이터를 안전하게 유지하면서 사용자 경험 향상.

🚩 요구사항 3: 소셜 로그인 리다이렉트 문제를 해결하라!

소셜 로그인 시 리다이렉트가 발생하면, 입력 중이던 데이터가 유실되거나 처리되지 않는 문제가 발생했습니다. 이를 해결하기 위해 리다이렉트 후에도 데이터를 안전하게 처리하는 흐름을 설계했습니다.

소셜 로그인 후 데이터 흐름

1. 리다이렉트 URL에 추가 정보 포함
로그인 요청 시 redirectTo 옵션에 데이터 처리 출처를 표시하도록 설정했습니다.

// src/utils/supabase/signIn.ts
await client.auth.signInWithOAuth({
  provider: 'google',
  options: { redirectTo: window.origin + '/auth/callback?from=google' },
});

// src/app/auth/callback/route.ts
const from = searchParams.get('from');

if (code) {
  const { error } = await supabase.auth.exchangeCodeForSession(code);
  if (!error) {
    return NextResponse.redirect(`${origin}${next}?from=${from}`);
  }
}

 

 

2. 리다이렉트 후 데이터 처리

리다이렉트된 페이지에서 데이터를 처리하고, 이후 원래 페이지로 돌아갑니다.

// src/utils/auth/authCallbackHandler.ts
const from = searchParams.get('from');
  if (userId && (from === 'google' || from === 'kakao')) {
   await saveSessionDataToSupabase(browserClient, userId);
  }
  router.replace('/');
};

 

결과

  • 리다이렉트로 인해 발생하던 데이터 유실 문제 해결.
  • 로그인 이후에도 사용자가 데이터 입력을 이어갈 수 있도록 경험 개선.

🚩 요구사항 4: 로그아웃 시 데이터 남지 않게 하라!

기존 사용자가 로그아웃한 후에도 캐시된 데이터가 남아 다음 사용자가 이전 사용자의 데이터를 볼 수 있는 보안 문제가 있었습니다. 이를 방지하기 위해 React Query의 캐시 무효화 기능을 적용하여, 로그아웃 시 유저 관련 데이터를 안전하게 초기화하도록 했습니다.

queryClient.invalidateQueries({ queryKey: QUERY_KEYS.guestBook(invitationId, 1) }); // 초대장 방명록 데이터
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.invitation() }); // 초대장 데이터
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.authUsers() }); // 인증된 유저 정보
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.invitationCard() }); // 초대장 카드 데이터

 

결과

  • 이전 사용자 데이터가 새로운 세션에서 노출되지 않음.
  • 보안성 및 데이터 정확도 향상

 

. 이번 개선 작업을 통해 회원과 비회원 사용자 모두에게 더 안정적이고 편리한 데이터 처리 경험을 제공할 수 있었습니다. 이번 글이 폼 데이터를 안전하게 관리하고 데이터 유실을 방지하는데 도움이 되길 바랍니다.!

 
 

 

 

 

 
 
 

'개발자 되는 법... > TIL...' 카테고리의 다른 글

Carousel  (1) 2024.11.07
홈 - 리뷰 캐러셀(개수 및 위치)  (0) 2024.11.01
홈 - 리뷰 캐러셀 (위치)  (0) 2024.10.31
홈 - 리뷰 캐러셀 구현  (0) 2024.10.30
마커 위도 경도 null 값 처리  (0) 2024.10.28