데브코스 3차 프로젝트 트러블 슈팅

3차 프로젝트 트러블 슈팅
트러블 슈팅프로젝트
avatar
2025.02.09
·
14 min read

1. 게시물 목록 페이지 watch 속성 적절한 사용

1차 문제

스터디 목록 페이지-> 프로젝트 목록 페이지로 이동시 페이지가 변하지않는 문제

원인

스터디, 프로젝트 페이지는 같은 router안에서 params의 값만 다르기 때문에 페이지가 변하지않아  반응형으로 처리되지 않는다.

    const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
        ...
       {
          path: 'PostList/:type',
          name: 'PostListPage',
          component: () => import('@/pages/PostListPage/PostListPage.vue'),
          meta: { showFooter: true, bg_color: 'bg-secondary-2' },
        },
]

스터디 목록 페이지 경로: ~/PostListPage/study

프로젝트 목록 페이지 경로: ~/PostListPage/project

해결

감시자 속성 watch로 현재 페이지의 params 상태 변화를 감시하는 방법 선택

(params값으로 게시물 유형 구분  즉,  프로젝트 ,스터디 구분)

watch(
  currentPostType,
  () => {
    currentPage.value = 1;
    fetchPostsWithPagination();
  },
  { flush: 'sync' },
);

2차 문제

watch 속성을 사용해 게시물 유형에 따라 데이터를 필터링 하는데까지는 구현했지만

만약 다른 필터링들을 걸어두고 유형을  바꾸면 초기화가 안되고 이전 필터링이 계속 걸려있게 된다.

해결

params 상태가 변할때  필터링의 상태값들을 전부 초기화시켜준다.

// 게시물 유형 감시(프로젝트 or 스터디)
watch(
  currentPostType,
  () => {
    currentPage.value = 1;
    selectedSkills.value = [];
    selectedPosition.value = '';
    selectedRecruitArea.value = '';
    selectedMeetingMethod.value = '';
    selectedRecruitStatus.value = '';
    fetchPostsWithPagination();
  },
  { flush: 'sync' },
);

3차 문제

배열타입인 스킬 부분을 제외하고 나머지는 여전히 필터링이 걸려있는채 게시물 타입만 바뀐다.

원인

필터링 상태 값을 변경시켜준다고  하더라도 비동기 함수인 fetchPostsWithPagination()이 호출되기 전에 변경된 값이 반영되지 않는듯 하다.

해결

변경된 필터링 값이 전달되는 FilterDropdown 안에 watch 속성 한번 더 사용

selected의 상태값이 변하는 것을 감시해 초기화된 상태값을 selectedItem에 전달해준다.


// 필터링 드롭다운 컴포저블
  <FilterDropdown
          :items="POSITION"
          :selected="selectedPosition"
          defaultText="모집 포지션"
          @click:select="handleSelectPosition"
  />


// FilterDropdonw 컴포저블
watch(
  () => props.selected,
  (newValue) => {
    selectedItem.value = newValue;
  },
  { immediate: true }, 
);


  //  셀렉트 박스 버튼 
    <button :class="selectBoxButtonStyle" @click="handleDropdownClick">
      <span>{{ selectedItem || defaultText }}</span>
      <img :src="dropdown_arrow_icon" alt="드롭다운 화살표" class="w-6 h-6" />
    </button>


2. 게시물 목록 페이지. tanstack query를 사용한 API호출 최소화

현재 필터링을 할 수 있는 경우가 다양하고 필터링 조건이 하나라도 달라지면 매번 API를 재호출하여 전달받은 데이터를 페이지네이션 처리하는 로직을 구현한 상태이다.

3332

문제

필터링 조건에의해 달라진 데이터를 기반으로 다시 페이지네이션 처리해야하기에 무수히많은 API재 호출은 피할 수 없다.

해결

tanstack vue query를 사용하여 한번 호출한 API는 캐시에 저장해두어 API 호출을 최적화 할 수 있다.

이전에 캐시에 저장된 데이터는 API 재호출할 필요없어 호출 횟수를 줄일 수 있었다.

TanStack Query
Powerful asynchronous state management, server-state utilities and data fetching. Fetch, cache, update, and wrangle all forms of async data in your TS/JS, React, Vue, Solid, Svelte & Angular applications all without touching any "global state"
https://tanstack.com/query/latest

tanstack query 사용 코드

const { isLoading, data, refetch, error } = useQuery({
  queryKey: ['filteredPosts', selectedFilters.value, currentPage.value],
  queryFn: fetchPostsWithPagination,
  staleTime: 1000 * 60 * 5, // 유통기한
  structuralSharing: true, // 변경되지않은 데이터 재사용
  placeholderData: (prev) => prev, // 대기 상태때 표시해줄 데이터
});


3. 디바운싱을 적용한 게시물 제목 검색

마찬가지로 검색기능에서도 tanstack query를 활용할 수 있다.

또한 API를 재호출해주는 refetch를 사용하여 입력 값이 바뀔때마다 api를 호출하고 한번 호출한 데이터는 캐시에 담아둘 수 있다.

문제

그러나 제목과 관련된 검색결과에 따른 API 호출은 무수히 다양하기 때문에 단순히 입력한 결과로. 새로운 api를 호출하는 것은  tanstackquery의 성능을 크게 보지 못할 것이라고 판단하였다.

해결

입력값이 변하면 바로 API를 호출하지않고 디바운싱 or 쓰로틀링 기법을 통해 무분별한 API호출을 막아 최적화

보통 디바운싱, 쓰로틀 모두 함수 호출을 최적화시켜주는 방법들이지만, 검색 기능에 자주 활용되는 것은 디바운싱이다. 반면에 쓰로틀링 기법은 보통 마우스 스크롤 이벤트에서 많이 적용된다.

따라서 디바운싱을 적용해 API 호출을 최적화 해주었다.

// 검색 결과를 담을 게시물
const searchInput = ref('');


//검색에 디바운싱  적용
let debounceTimeout;

watch(searchInput, (newValue) => {
  clearTimeout(debounceTimeout);
  debounceTimeout = setTimeout(() => {
    selectedFilters.value.searchResults = newValue;
  }, 300);
});

const handleInputSearch = (input) => {
  searchInput.value = input;
};

0.3초뒤 입력 값에 대한 게시물이 필터링되는 모습

3331

4. 마이페이지, 유저페이지 게시물 작성,신청,북마크 목록 실시간으로 반영되지 않는 문제

문제

게시물 신청하기를 누르고 마이페이지로가면 신청한 게시물이 추가가 되어있어야하는데 새로고침, 필터링 변화를 해줘야만 신청 목록에 보이는 문제

게시물 신청을 했음에도 실시간으로 반영되지 않는 모습

3333

새로고침해야 보이는 문제

3334

원인

게시물 목록을 불러오는 API를 이전에 tanstack query를 활용하여 캐싱시켜주었기 때문에 API를 재호출하는 조건(필터링 상태 값의 변경)을 만족하지 않는한 staleTime동안은 받아올 데이터가 달라지더라도 캐싱된 데이터가 변하지 않는다.

const { isLoading, data, refetch, error } = useQuery({
  queryKey: ['filteredPosts', selectedFilters.value, currentPage.value],
  queryFn: fetchPostsWithPagination,
  staleTime: 1000 * 60 * 5, // 유통기한
  structuralSharing: true, // 변경되지않은 데이터 재사용
  placeholderData: (prev) => prev, // 대기 상태때 표시해줄 데이터
});

해결

해결법에는 크게 3가지가 있었다.

  1. queryClient.invalidateQueries()를 사용해 특정 쿼리의 캐시를 무효화하고 새로운 데이터를 가져오기

  2. refetch()를 사용해 새 데이터 강제로 가져오기

  3. 조건을 통해 마이 페이지에 적용된 tanstack Query에는 staleTime과 gcTime을 적용시켜주지않고(캐싱시켜주지않고) 매번 새로운 API 호출

이중에서 3번째 방법으로 해결하였다. 처음에는 첫번째 방법으로 해결하려 했지만

게시물 작성, 북마크, 신청 같은 액션은 사용자에 의해 자주 일어날 것이라 판단하여 그럴때마다 굳이 쿼리의 캐시를 무효화하는 것 보다는 처음부터 캐싱 시켜주지 않는 방식이 적합하다고 판단했다.

staleTime, gcTime 조건 처리

export const usePagination = (fetchData, queryKey, filters = {}, enableCache = true) => {
  // 현재 페이지, 전체 페이지
  const currentPage = ref(1);
  const totalPage = computed(() => data?.value?.total_page || 1);

  // 필터링된 게시물
  const filteredPosts = computed(() => data?.value?.posts || []);

  // 필터링
  const selectedFilter = ref(filters);
  
  const { isLoading, data, refetch } = useQuery({
    queryKey: [queryKey, selectedFilter.value, currentPage.value],
    queryFn: fetchData,
    staleTime: enableCache ? 1000 * 60 * 5 : 0, // 유통기한
    gcTime: enableCache ? 1000 * 60 * 5 : 0,
    structuralSharing: true, // 변경되지않은 데이터 재사용
    placeholderData: (prev) => prev, // 대기 상태때 표시해줄 데이터
  });


5. 내가 남한테 좋아요를 눌러도 내 알림에  뜨는 문제, 나머지 유저들의 알림에도 알림이 가는 문제

우선 알림 로직에 대해 간단히 설명하자면,

게시물 좋아요의 경우,

  1. 사용자가 나의 게시물에 엑션을 취할때 예를들어 좋아요를 누른다.

  2. post_like 테이블에 새 레코드가 쌓인다.

  3. 사전에 만들어둔 트리거가 발동한다.

  4. 트리거에 의해 like 테이블에 새로 추가된 레코드의 정보를 활용해 

    notifications 테이블에 새 레코드를 추가하는 함수가 실행

  5. notifications 테이블에 새 레코드가 추가되면 supabase의 구독기능을 활용해  해당 게시물 작성자에게 알림을 보낸다.

post_like 테이블

3329

notifications 테이블

3330

문제

내가 남한테 좋아요를 눌러도 내 알림에  뜨는 문제, 또한 나머지 유저들의 알림에도 알림이 가는 문제

export const getNotifications = async () => {
  const user = await getUserInfo();

  const { data, error } = await supabase
    .from('notifications')
    .select()
    .eq('user_id', user.user_id)
    .order('created_at', { ascending: false });
  if (error) {
    console.error(error);
  }
  console.log(data);

  return data;
};

 notifications 테이블에서 현재로그인한 사용자의 user_id와 같은 목록만 알림 목록에 보여지게 로직을 짰다.

원인

위 코드처럼 알림목록 자체를 받아오는 로직은 구현하였지만 notifications에 새 레코드가 들어올때 해당 게시물 작성자에게만 알림이 오도록 프론트에서 처리 해줘야하는데 그 부분이 처리가 안돼서 모든 사용자에게 알림이 가게 된 것이다.

잘못된 코드

// 새로운 알림 구독 (실시간 반영)
export const subscribeToNotifications = (addNotifications, setHasNewNotificationTrue) => {
  return supabase
    .channel('notifications')
    .on(
      'postgres_changes',
      { event: 'INSERT', schema: 'public', table: 'notifications' },
      async (payload) => {
        console.log('새로운 알림 도착!', payload.new);
        addNotifications(payload.new); // 프론트단에서 알림 목록에 추가
        setHasNewNotificationTrue(); // 새 알림이 왔는지 유무 true, false
      },
    )
    .subscribe();
};

해결

구독 기능에도 프론트단에서 현재 로그인한 사용자의 user_id와  알림테이블에 추가된 레코드의 user_id(작성자)가 같을때만 새 알림을 추가해주는 로직을 넣어주어 해결하였다.

// 새로운 알림 구독 (실시간 반영)
export const subscribeToNotifications = (addNotifications, setHasNewNotificationTrue) => {
  return supabase
    .channel('notifications')
    .on(
      'postgres_changes',
      { event: 'INSERT', schema: 'public', table: 'notifications' },
      async (payload) => {
        const { useUserStore } = await import('@/stores/user');
        const userStore = useUserStore();
        const { user } = storeToRefs(userStore);
        if (payload.new.user_id === user.value.user_id) {
          console.log('새로운 알림 도착!', payload.new);
          addNotifications(payload.new);
          setHasNewNotificationTrue();
        }
      },
    )
    .subscribe();
};







- 컬렉션 아티클