
상황
서버 상태관리를 React Query로 관리하기 시작하면서 기존에 fetch로 만들었던 리퀘스트 함수들을 AXIOS로 싹 갈아 엎기로 했다. AXIOS 자체가 러닝 커브가 낮은 편이라 갈아 엎는 작업 자체는 금방 끝났다. 그런데 npm run dev를 때리자 getServerSideProps에서 즉시 에러가 튀어나왔다.
문제
Error serializing .dehydratedState.queries0.state.data.headers returned from getServerSideProps in "/mydashboard".
"/mydashboard"페이지에서 사용한 getServerSideProps 함수에서 serializing 에러가 발생했다. 메세지만 놓고 보면 아주 단순한 에러이다. 여기서 serializing은 '직렬화'라고 번역되는데, 컴퓨터 메모리 상에 존재하는 객체(Object) -> 문자열(string) 로 변환하는 작업을 말한다. 그 반대는 Deserialization 혹은 Parse라고 부르고 '역직렬화'라고 번역한다. JSON.parse() 메소드가 하는 일이 바로 역직렬화이다.
dehydratedState에 대하여 정확히 알지는 못하지만, react query를 컴포넌트가 props로 넘겨받는 과정에서 탈수 과정을 거치는데 그에 관한 이야기라고 생각했다. 따라서 나는 다른 부분은 고려하지 못하고 dehydrate 과정에서 무언가 문제가 발생했겠거니 생각하며 열심히 삽질을 시작했다.
해결
결론부터 말하자면 내 초기 아이디어가 그 자체로 잘못된 건 아니었다. 이 에러는 dehydrate 과정에서 직렬화 하는 중 문제가 발생했기에 튀어나온 것이다. 그런데 왜 문제가 발생한 걸까? 이를 위해서는 JS에서 JSON 데이터로 직렬화하는 방식을 알아야 한다. 객체를 JSON 데이터로 변환하는 과정에서 메서드나 던더 프로토, getter/setter 등과 같이 JSON으로 변경할 수 없는 프로퍼티는 모두 무시된다.
AXIOS Instance가 반환하는 값을 그대로 dehydrate에 넣었다. 그런데 반환하는 값에 무언가 JSON으로 변경할 수 없는 프로퍼티가 존재하고, 그것을 dehydrate하려고 시도했기 때문에 이러한 직렬화 오류가 발생한 것이라 생각된다.
이러한 오류를 해결하려면 크게 두 가지 방법을 사용할 수 있다. 좀 더 널리 알려진 해결 방법은 getServerSideProps에서 AXIOS가 리턴한 값을 가지고 있는 queryClient를 JSON.parse(JSON.stringify(dehydrate(queryClient)))와 같이 직렬화 ➠ 역직렬화를 하여 문제가 될만한 놈들을 사전에 걷어내는 것이다. 하지만 나는 이것이 '깔끔한' 해결 방법이라고는 생각하지 않는다. 좀 지저분하달까?
내가 선택한 해결 방법은 AXIOS Instance를 request라는 함수로 한 번 감싼 다음, 필요한 데이터 ─ data, status 등 ─ 만을 반환하게 하는 것이다. 이 방법 역시도 완전한 해결책이라고는 생각하지 않는다. AXIOS 리턴 값 중에서 반환되지 않는 값이 있다면 그 때마다 계속 request 함수가 반환하는 값을 건드리게 될 테니까. 하지만 직렬화 ➠ 역직렬화하는 것보다는 낫다고 생각한다.
결국은 각자의 취향 문제로 귀결될 것 같지만.
결론
결론이랄까… '와! 이 문제를 해결했다!'라고 선언하기에는 아무래도 조금 거식한 데가 있다. dehydrate 함수가 하는 일이 정확히 어떻게 되는지 모르기 때문이다. 공식문서를 읽어봐도 시원하게 '이게 이런 식으로 동작해서 직렬화 오류가 발생한 거구나'하는 식의 깨달음은 얻지 못했다. 다만 한 가지 확실한 것은 이게 JSON.stringify 처럼 동작하지는 않는다는 거겠지…
문제를 해결하면 찾아봐야 할 게 또 생기고, 이렇듯 배움에는 끝이 없다는 게 오늘의 결론인가보다.
더 읽어보기
2026.03.01
렌더링 전략 정리
리액트와 Next.js에서 렌더링 전략은 단순한 옵션 선택이 아니다. 이는 서비스의 초기 로딩 속도, 서버 비용, 캐싱 전략, SEO 노출, 개발 복잡도까지 동시에 좌우하는 아키텍처 결정이다. 프로젝트 규모가 커질수록 “어디에서 HTML을 생성하는가”, “언제 자바스크립트를 실행하는가”…
2025.07.15
앱 라우터를 싫어하게 된 몇 가지 이유
지금까지 진행한 대부분의 프로젝트에서는 Next.js의 페이지 라우터를 사용해왔지만, 이번에는 처음으로 앱 라우터(App Router)를 도입해보았다. 특별한 이유가 있었던 것은 아니고, 그저 기술적인 호기심 때문이었다. 서버 수준에서 처리되는 fetch cache나 서버 컴포넌트 개념…
2025.06.05
Next.js 데이터 캐시와 크롬 개발자 도구
상황 나는 page router를 좋아하지만, 이번에는 여러 사정이 있어서 app router를 쓰게 되었다. 이왕 쓰게 된 김에 이런 저런 기능을 몽땅 활용할 생각이었다. 가장 기대하던 기능은 데이터 캐싱이었는데, 가능한 많은 걸 캐싱해두면 랜더링 완료 시점을 한참 일찍 앞당길 수 있…
2025.05.26
3. SharedWorker로 멀티탭 소켓을 하나로 묶기
같은 서비스를 10개의 탭으로 열었을 때 서버에 소켓 연결도 10개가 생긴다면, 문제는 실시간 기능 자체가 아니라 브라우저 컨텍스트마다 연결을 새로 만드는 구조에 있을 수 있다. 채팅, 알림, 협업 상태처럼 페이지가 열려 있는 동안 유지되는 연결은 탭 수만큼 늘어날 때 서버와 클라이언트…
2026.06.01
React Server Components를 위한 컴포넌트 아키텍처
이 포스트는 Vercel의 Next.js 팀 소속 개발자 Aurora Scharff가 자신의 블로그에 올린 Component Architecture for React Server Components 게시글을 번역한 것이다. 번역하는 과정에서 다소 의역이 있을 수 있으며, 일부 번역에는…
2026.05.26
차트는 멈췄는데 윈도우가 움직인다
상황 어느날 서비스를 살펴보시던 팀장님께서 이런 말씀을 slack에 남기셨다. 진호님, 예측 차트에서 zoom을 계속하면 어느 순간 라인 차트가 아니라 단일 스캐터 차트처럼 보이는 데, 이거 수정하면 좋을 거 같아요. 어느정도 zoom을 하면 그 이후로는 zoom이 안 되도록 할 수 없…
2026.05.21
피자가게로 이해하는 디자인 패턴
에이든 피자는 처음부터 복잡한 시스템을 만들 생각이 없었다. 처음에는 메뉴 몇 개만 만들면 됐다. 그런데 손님은 커스텀 주문을 넣기 시작했고, 주방은 상태를 나눠야 했고, 결제와 배달앱과 알림이 하나씩 붙었다. 코드도 가게를 닮는다. 장사가 잘될수록 이상하게 더 쉽게 망가진다. 디자인…
2026.05.21
7. Decorator — 토핑 추가할 때마다 클래스를 새로 만들 수 없다
에이든 피자에서 주문서를 객체로 만들자 취소와 재주문은 한결 편해졌다. 그런데 주문이 편해지자 손님들도 한결 편해졌다. 편해진 손님은 더 많은 요구를 한다. "치즈 추가요", "올리브도 추가요", "소스 많이요", "조금 더 바삭하게 구워주세요" 같은 요청이 주문대 위로 쌓이기 시작했다…
댓글
댓글을 불러오는 중...