SPA(Single Page Application) 개발에서 고려할 사항
React를 사용해서 웹 프론트 개발을 한지는 꽤 오래 되었지만 SPA(Single Page Application)으로 하기에는 너무 복잡할것 같아서 고전적인 방법인 페이지당 React를 mount해서 사용하는 식으로 사용해왔다.
이번에 새로 출시한 서비스인 박스 히어로는 ClojureScript를 이용해서 SPA방식으로 구현하였다.
SPA가 마치 대세인것 같고 훨씬 좋아 보이지만 보기와는 다르게 고려해야 할 점들이 꽤 있다.
데이터(State) 관리는 어떻게 할 것인가?
고전적인 방식에서는 웹페이지가 로드 될때 서버에서 한번만 데이터를 주거나, 페이지가 로드 된 다음 API를 이용해서 데이터를 가지고 오면 됐다. 다른 페이지가 새로 로드 되면 기존에 있던 데이터는 모두 날아가면 됐기 때문에 페이지마다의 Top level에서 State를 관리하면 된다.
그런데 SPA로 구현하게 되면 페이지당 State관리가 아닌 전체 Application의 State를 가지고 있어야 하는데 관리해야할 전체 데이터의 양이 크고, State 깊이가 엄청 깊어질 수 있다.
이런 이유로 React.js로 구현하면 Redux를 거의 필수로 사용해야 한다. ClojureScript쪽에는 유명한 Re-frame 라이브러리가 있는데, 근본적으로 Event방식으로 Global State를 변화시킨다는 점에서 Redux와 비슷하다고 볼 수 있다. State관리를 위한 라이브러리를 사용한다고 해도, 거의 모든 State변화에 따른 Event를 따로 정의해서 만들고 해야 하기 때문에 꽤 번거로운 작업이 될 수 있다.
언제 새로운 데이터를 가져올 것 인가?
SPA시대 이전에는 페이지가 로드 될때 시점에 DB에 있는 데이터를 가져와서 보여주고, 사용자가 새로고침을 누르는 순간이 새로운 데이터를 가져오는 시점이다.
반면 SPA에서는 기본적으로 페이지 로드가 없고, 모든 페이지는 단순히 HTML5 History에 의해서 렌더링만 변화 될 뿐이다. 그래서 고전 방식에 비해서 언제 새로운 데이터를 불러와야 할지를 스스로 정해서 구현해야 한다.
가장 바보 같지만 쉬운 방식은 새로운 페이지(History)가 렌더링 될때마다 필요한 데이터를 API를 통해서 가져오는 방식이다. 이 방식도 꽤 괜찮지만 Anchor(주소창의 #)를 통해서 History를 관리한다면 같은 페이지를 열면 새로 클릭했다는 정보가 누락되서 다시 불러오기가 안된다.
예를 들면 http://www.boxhero.io/page#/setting 의 주소에서 동일한 주소를 가리키는 설정 버튼을 눌렀을때 설정페이지에서 필요한 데이터를 다시 가져올 수 없다.
새로운 코드가 Deploy되었을때 처리
SPA 방식으로 구현된 웹페이지는 유저도 모르게 꽤 오랜 기간 동안 페이지 새로고침을 안할 가능성이 있다. 만약 Javascript쪽에 버그가 있어 deploy를 했는데 유저들은 새로고침을 안하고 계속 사용하기 때문에 버그가 있는 버전을 계속 사용하게 된다.
박스히어로에서는 서버에 새로운 코드가 deploy가 될때 고유한 build id와 같은 값이 발급되는데, 이 id가 기존과 동일한지 체크하도록 해서 새로고침이 필요한지를 판단하게 하였다. id를 체크하는 시점은 모든 API에 빌드 버전을 Response 헤더에 추가해서 어떤 API이든 부를때 체크한다.
Access-Control-Allow-Origin:*
Connection:keep-alive
Content-Encoding:gzip
Content-Length:5723
Content-Type:application/transit+json
Date:Wed, 07 Feb 2018 14:40:06 GMT
Etag:2147400033
Server:http-kit
Via:1.1 vegur
X-Boxhero-Asset-Version:0WVfGhHhIFTlX0f5
페이지 에러 핸들링
jQuery로 간단하게 Javascript코딩을 하던 시절과 다르게 SPA에서는 간단한 Script오류를 내서는 안된다. 왜냐면 이제 Javascript로 하나의 Application을 만들어 낸것과 동일하고, 잘 안된다고 새로고침 해 볼 수도 없다.
제일 간단한 방법으로는 entry point에 try catch로 묶는 방법이다.
try {
app.main();
} catch(err) {
alert('오류!');
}
모바일앱 개발쪽에서 Crashlytics를 사용하면, 모바일 앱을 사용하다가 crash가 나는 경우 call stack, OS버전, 하드웨어 사양과 같은 정보와 함께 서버에 보내져서 추후에 쉽게 디버깅을 할 수 있다.
웹클라이언트에 Crashlytics와 같은 서비스가 없을까 찾아봤더니 Sentry라는 좋은 서비스가 있었다. 위의 try catch방식과 근본적으로는 비슷하지만, 어떤 브라우져인지 callback, deploy 버전등의 추가적인 정보와 함께 확인하고 디버깅이 가능하다. 또한 에러가 발생하면 바로 이메일로 알림을 받을 수 있어, 빠르게 버그 대응이 가능해진다.
결론
SPA로 웹프론트 개발을 하는것은 생각보다 고려해야 할 점이 많고, 가능하다면 SPA보다는 고전적인 방식으로 구현하는것을 추천한다. 하지만 고전 방식에 비해 반응속도가 훨씬 빨라지고, 코드 재사용이 훨씬 용이하다는 점이 매력적이긴 하다.
누군가 박스히어로는 왜 SPA 방식으로 구현했냐고 물어본다면, 사실 대단한 이유는 없고 그냥 한번 해봤다. 안그랬음 이런 내용들을 몰랐었겠지 ;-)