Skip to Content
FrontendPogoAtlas의 React 전환과 서버 렌더링
🎨 Frontend2026년 10월 17일

PogoAtlas의 React 전환과 서버 렌더링

#react#ssr#hydration#pokemon-go#frontend
PogoAtlas API와 웹 구축2 / 3
PogoAtlas 웹을 React와 공통 컴포넌트로 전환하고 서버 렌더링을 추가했습니다. 입력을 유지하는 재시도와 URL 구성, 초기 데이터 재사용 과정을 정리합니다.

PogoAtlas 웹은 도감과 레이드, 순위, 계산기를 보여주는 클라이언트로 시작했습니다. 기능이 늘면서 카드와 입력 처리가 반복됐고, 이후 React와 공통 디자인 패키지로 옮겼습니다.

React 전환 뒤에는 직접 주소를 열었을 때의 HTML도 보완했습니다. 브라우저에서 조작할 수 있는 화면과 처음 응답에 본문을 제공하는 것은 별도의 작업이었습니다.

오류 표시와 재시도

초기 화면에는 추천 후보의 기준을 실제 순위처럼 읽게 만드는 문구가 있었습니다. 순위와 추천 기준, 배울 수 있는 기술과 추천 조합을 나눠 표시했습니다.

요청에 실패하면 내부 오류를 그대로 보여주는 대신 사용자가 다음에 할 수 있는 동작을 안내하도록 했습니다. 재시도할 때는 필터와 입력을 유지했습니다. 계산 실패 때문에 화면까지 초기화되면 같은 값을 다시 입력해야 하기 때문입니다.

화면 정보구분한 내용
순위실제 순위와 추천 후보 기준
기술배울 수 있는 목록과 추천 조합
오류사용자 안내와 내부 예외
재시도입력 유지와 화면 초기화

URL 구조와 모바일 화면

도감과 메뉴는 해시만 사용하는 대신 실제 경로로 열 수 있게 했습니다. 기존 링크를 유지하면서 상세와 폼을 직접 열고 새로고침할 수 있도록 구성했습니다.

작은 화면에서는 카드와 여백을 줄였습니다. 필요한 수치의 의미를 생략하기보다 같은 화면에서 읽을 수 있는 정보량을 조정했습니다.

API와 DB를 유지한 React 전환

주요 화면을 React 컴포넌트로 나누고 기존 Node 서버에 번들을 연결했습니다. API와 수집기는 유지하고 메뉴와 폼, 데이터 패널에는 공유 패키지를 사용했습니다.

조회 상태도 정리했습니다. 이전 요청이 늦게 도착해 새 검색 조건의 화면을 덮지 않도록 했습니다. 오늘·내일을 바꿀 때는 기존 카드를 유지하고, 요청이 오래 걸릴 때만 고정된 공간에 작은 로딩 표시를 넣었습니다. 로딩 문구가 화면을 계속 밀지 않게 한 변경입니다.

서버 렌더링과 초기 데이터

검색엔진의 실제 URL 테스트에서는 화면에 필요한 API가 robots 규칙에 막힌 것을 확인했습니다. 공개 화면의 리소스 경로를 열고, 직접 접속했을 때 서버에서 본문을 렌더링하도록 보완했습니다.

다이어그램 로딩 중...

서버가 조회한 초기 응답은 HTML과 함께 브라우저로 넘겼습니다. 브라우저는 같은 자료를 다시 요청하지 않고 초기 화면에 사용합니다. 이후 날짜나 필터를 바꿀 때 필요한 조회를 추가합니다.

서버 HTML에 React 동작을 연결할 때는 서버와 브라우저의 초기 내용이 맞아야 합니다. 같은 빌드의 서버와 브라우저 번들을 배포하고 초기 자료도 재사용하도록 한 이유입니다. 날짜가 바뀌면 캐시된 자료도 갱신하도록 했습니다.

공개 HTML과 특정 URL의 렌더링은 확인했습니다. 모든 주소의 검색 색인까지 완료된 결과는 아닙니다. 초기 클라이언트, React 전환, 서버 HTML 보완은 같은 제품에서 필요가 생길 때마다 이어간 변경입니다.

화면 요청이 뒤바뀌는 경우

빠르게 검색 조건을 바꾸면 앞선 요청이 나중에 끝날 수 있습니다. 마지막에 도착한 응답을 항상 표시하면 현재 선택은 내일인데 오늘 자료가 나타나는 문제가 생길 수 있습니다. 실제 전환 문서의 useAsync·useResource도 새 요청 뒤의 이전 응답을 무시하도록 구성합니다.

다음은 요청 순서 확인을 설명하는 작은 예시입니다. 실제 Hook 전체를 복사한 코드는 아닙니다.

javascript
example/latest-request.js
let requestVersion = 0; async function load(url, apply) { const version = ++requestVersion; const response = await fetch(url); if (!response.ok) throw new Error(`HTTP ${response.status}`); const data = await response.json(); if (version === requestVersion) apply(data); }

이 예시는 자료를 반영할 순서만 보여줍니다. 실제 React 화면에는 컴포넌트 해제와 오류 표시, 요청 취소도 고려해야 합니다. 취소만으로 이전 응답의 반영을 모두 막았다고 가정하지 않고 현재 요청과 맞는지를 함께 봅니다.

날짜 전환 중에는 기존 카드를 유지하고 현재 선택한 버튼의 고정 공간에서 로딩을 표시했습니다. 입력과 화면을 매번 비우면 짧은 요청에도 페이지가 흔들리고, 실패 후 같은 조건으로 재시도하기도 어려워집니다. 재시도는 상태 전체 초기화보다 실패한 조회를 같은 입력으로 다시 실행하는 동작으로 뒀습니다.

서버 HTML과 초기 데이터의 일치

서버가 본문을 렌더링하고 브라우저가 다른 초기 자료로 시작하면 hydration이 맞지 않을 수 있습니다. 서버에서 조회한 자료를 첫 화면과 브라우저 초기 상태에 함께 사용해야 합니다.

text
example/ssr-contract.txt
같은 요청의 URL·한국 날짜·폼 조건 → 서버의 API 응답 → HTML 본문 + 초기 자료 → 브라우저의 같은 첫 화면 → 사용자 조건 변경 후 추가 조회

초기 자료를 HTML에 JSON으로 넣을 때는 script 태그의 문맥도 고려해야 합니다. 다음은 <가 들어 있는 문자열을 직렬화하는 예시입니다. 실제 서버의 전체 응답 함수는 아닙니다.

javascript
example/initial-data-json.js
const serializeInitialData = value => JSON.stringify(value).replace(/</g, '\\u003c'); const initialJson = serializeInitialData({ title: '</script><sample>' });

HTML 문자열에 사용자의 자료를 그대로 이어 붙이지 않고, 본문은 렌더러의 텍스트 처리와 초기 자료의 직렬화를 구분해야 합니다. 브라우저에서는 이 JSON을 읽어 같은 초기 상태로 연결합니다. React hydrateRoot 문서 도 서버와 클라이언트의 초기 출력 일치를 설명합니다.

배포도 서버와 브라우저의 한 쌍으로 봅니다. 현재 전환 문서는 콘텐츠 해시가 있는 번들을 먼저 준비하고 HTML을 마지막에 반영하도록 정리합니다. 새 HTML이 아직 없는 이전 번들을 요구하거나 반대 조합이 생기지 않게 하기 위해서입니다.

이번 전환에서는 API와 수집기를 유지하면서 화면을 React로 옮기고, 직접 접속한 URL에서도 서버가 본문을 내려주도록 보완했습니다. 브라우저는 그 초기 자료를 재사용하고 이후 조작에 필요한 자료를 요청합니다. 공개 HTML과 특정 도감 URL의 렌더링까지 확인했으며, 모든 URL의 검색 색인이 끝났는지는 별도로 확인해야 합니다.

참고 자료

Last updated on