Playball Logo

Command Palette

Search for a command to run...

목차 열기
문제

lighthouse 성능 개선을 위해 홈 화면 SSR 전환 후 일부 GET 요청 실패

기존 CSR 구조에서는 홈 진입 후 브라우저에서 다음 데이터를 조회했다.

  • 경기 목록 조회
  • 구단 목록 조회

SSR 전환 후에는 page.tsx에서 서버 사이드로 데이터를 미리 조회한 뒤, 클라이언트 컴포넌트에 props로 전달하는 구조로 변경했다.

그러나 SSR 전환 이후 홈 화면에서 일부 GET 요청이 정상적으로 동작하지 않는 문제가 발생했다.

대표적으로 구단 목록 조회 요청이 정상 응답하지 않거나, 서버 로그에서 403 응답이 확인되었다.

원인 분석
1. CSR과 SSR의 요청 실행 환경이 다름

CSR에서는 API 요청이 브라우저에서 실행된다.

즉 요청 주체가 사용자의 브라우저이며, 브라우저 환경에서 필요한 쿠키, 클라이언트 헤더, 공통 fetch 설정이 함께 적용될 수 있다.

반면 SSR에서는 API 요청이 브라우저가 아니라 Next.js 서버에서 실행된다.

따라서 SSR로 변경하면 API 서버 입장에서는 요청 출처와 실행 환경이 달라지므로 이로 인해 기존 CSR에서는 정상 동작하던 GET 요청이 SSR 환경에서는 실패할 수 있다.

2. 기존 API wrapper를 우회하면서 요청 헤더가 달라짐

기존 클라이언트 코드에서는 getMatches, getClubs 가 내부적으로 공통 API wrapper를 사용하고 있다.

// 예시
export const getMatches = async (date?: string) => {
  const params = date ? `?date=${date}` : "";
  return await pub.get<MatchesData>(`${API_BASE_URL}/order/matches${params}`);
};

여기서 pub.get은 단순 fetch가 아니라 공통 요청 설정을 포함하는 wrapper다.

wrapper에 공통 base config, Content-Type, 인증 관련 헤더와 같은 정보가 삽입 되어있지만 SSR 전환 과정에서 서버 용 함수를 만들며 직접 fetch()을 사용함으로 기존 pub.get이 자동으로 붙여주던 요청 설정이 빠졌다.

따라서 SSR용 fetch 요청이 기존 CSR 요청과 동일한 형태로 만들어 지지 않은 것이다.

해결 방법

SSR을 적용해도 홈 화면 전체가 서버 중심으로 단순해지는 구조가 아니며 이미 홈 화면은 브라우저 상태와 상호 작용 의존도가 높기 때문에 CSR로 유지하는 것이 현재 구조에서 더 안정적이라고 판단했다.

따라서 홈 화면은 CSR 구조를 유지하고, 성능 개선은 다음 방식으로 진행했다.

  1. LCP 이미지 priority 적용
  2. 스켈레톤 크기를 실제 카드 크기와 동일하게 조정
  3. 초기 loading 상태를 true로 설정해 layout shift 방지
  4. 날짜 계산은 클라이언트 마운트 이후 수행
  5. 구단 카드/경기 카드 스켈레톤 크기 고정