본문으로 바로가기
반응형

프런트엔트 라이브러리를 공부하다 보면 "단일 페이지 애플리케이션 (SPA)"이라는 용어가 자주 등장합니다. 

이번 글에서는 앵귤러 및 기타 프론트엔드 라이브러리 또는 프레임워크에서 자주 등장하는 SPA(Single Page Application)의 개념과 특징들에 대해서 알아보도록 하겠습니다.

 

 

이런 SPA 아님.

 

1. Single Page Application (SPA)의 의미

가장 기본적인 의미에만 충실하게 해석한다면, SPA(Singple Page Application, 단일 페이지 애플리케이션)는 말 그대로 하나의 페이지로 구성된 애플리케이션입니다. 사용자가 요청한 각각의 페이지를 서버가 생성해서 전달해주는 것이 아니라, 클라이언트가 동적으로 페이지를 다시 작성하는 방식이죠.

 

첫 페이지 요청시 단 한 번만 리소스(HTML, CSS, JavaScript)를 로딩하고, 그 이후로는 페이지 리로딩 없이 필요한 부분만 서버로부터 받아서 화면을 갱신합니다. 필요한 부분만 갱신하기 때문에 네이티브 앱에 가까운 자연스러운 페이지 이동과 사용자 경험(UX)을 제공할 수 있습니다. 만약 데이터가 전혀 필요 없다고 한다면 서버와 통신할 일은 더 이상 없겠죠?

 

SPA 방식은 Angular, React, Vue.js 등 걸출한 프론트엔드 기술들이 나오면서 크게 유행하고 있습니다. SPA와 대비되는 의미로 페이지 별로 서버에서 받아오는 방식에 대해서는 MPA (Multi Page Application)이란 용어를 사용합니다.

 

SPA에서는 주로 클라이언트 쪽에서 화면을 구성하므로 Client Side Rendering (CSR)으로 불리기도 합니다. MPA의 경우는 주로 서버가 화면을 구성하기 때문에 Server Side Rendering (SSR)이라고 부릅니다.

 

엄밀하게 따지면 SPA에서도 SSR을 섞어 사용할 수 있고, MPA에서도 일부 CSR을 섞어 사용할 수 있기 때문에 완전하게 동일하다고 볼 수는 없습니다. 다만 일반적인 개념으로 이해하기 위해서 SPA = CSR, MPA = SSR 이라고 생각하고 진행하겠습니다.

 

 

아래 그림은 개념적으로 MPA와 SPA를 비교한 것입니다.

제 마음대로 그린 것이니 단순한 개념 정도로만 생각해주세요~

Landing과 Page1은 Static 한 페이지, Page2는 데이터 베이스를 참조해야 하는 dynamic 한 페이지라고 가정합니다.

 

 

MPA에서는 페이지 렌더링과 관련된 작업을 서버에서 처리하고, 클라이언트에 최종적으로 완성된 문서를 전송합니다. 반면 SPA에서는 처음에 모든 내용(주로 JavaScript 형태)을 전달하고, 이후에는 클라이언트가 알아서 렌더링 하여 화면을 구성합니다. 페이지별로 필요한 데이터가 있으면 서버에 별도로 요청해서 반영하게 됩니다.

 

2. SPA의 장단점

SPA와 같은 방식으로 웹 서비스를 운용하는 경우 다음과 같은 장, 단점을 가지고 있습니다.

SPA 장점

  1. 클라이언트가 모든 페이지를 가지고 있으므로 앱과 같은 자연스러운 사용자 경험을 제공합니다. 사실상 로딩 이후에는 모바일 앱과 동일한 방식으로 동작을 한다고 볼 수 있습니다.
  2. 페이지를 이동 하더라도 필요한 부분 (컴포넌트)만 부분적으로 교체하면 되므로 효율성이 증가합니다.
  3. 서버가 해야 할 화면 구성을 클라이언트가 수행하므로 서버 부담이 경감됩니다.
  4. 모듈화 또는 컴포넌트별 개발이 용이합니다.
  5. 백엔드와 프론트엔드가 비교적 명확하게 구분됩니다.
  6. 앱과 웹이 동일한 서버를 이용할 수 있습니다.
  7. PWA(Progressive Web App)과 궁합이 잘 맞습니다.

 

SPA는 일단 로딩이 된 후에는 네이티브 앱과 비슷한 수준의 향상된 사용자 경험(UX)을 제공할 수 있습니다. 중복되는 리소스 없이 필요한 요청만 하게 되므로 효율성이 증가하는 측면도 있겠죠?

또한 최근의 프론트엔드 라이브러리는 HTML 히스토리 모드를 지원하므로, SPA에서도 자연스러운 URL 이동을 구현할 수 있습니다.

 

서버는 초기 파일 전송 후에는 API 처리만 해줘도 됩니다. 서버 측면에서 웹과 모바일 앱에 공통으로 적용할 여지가 많아집니다. 앞단에서 NginX로 Static 파일을 처리하고 필요한 데이터는 API를 통해서 뒤에서 Node.JS 등으로 처리하는 방식도 많이 사용되는 것 같습니다.

 

SPA 단점

  1. 처음 접속시에 사이트 구성과 관련된 모든 리소스를 한 번에 받기 때문에 초기 구동 속도가 느릴 수 있습니다.
  2. 데이터를 별도로 요청하고 받아와서 화면을 구성하므로 설계 방식에 따라서는 화면이 변하는 모습이 사용자에게 노출될 수 있습니다. 별로 보기 좋지는 않습니다.
  3. SPA 구조 상 데이터 처리는 클라이언트에서 하는 경우가 많은데, 중요한 비즈니스 로직이 존재한다면 사용자가 굳이 파헤쳐 보겠다 하면 막기 어렵습니다.
  4. 검색엔진 최적화(SEO)가 어렵습니다.

 

1번과 2번 단점은 lazy loading, pre-rendering을 통해서 어느 정도 극복이 가능합니다. (이 부분은 다음 챕터에서 다시 다루도록 하겠습니다)

3번과 같은 경우, 노출되어서는 안 되는 중요한 로직이 있다면 해당 기능만 API를 통해서 서버에서 처리하도록 하면 그만이니까 다소 비효율적이긴 하더라도 큰 문제는 아닐 것 같습니다.

 

사실 근본적인 문제는 결국 SEO 입니다. 이는, 검색 엔진이 크롤링할 때 JavaScript를 실행하지 않고 있는 그대로 긁어가기 때문에 문제가 발생합니다. 즉, 사용자가 인지하는 화면과 검색 엔진이 인지하는 화면이 다르게 되는 것이죠. 요즘 구글은 JavaScript 처리도 가능하다고 하는 것 같은데 아직은 완벽하게 될지는 조금 의문이고 검색엔진은 구글만 있는 게 아니니까...

 

전통적인 MPA의 경우 사용자 단에서 이미 완성된 형태의 템플릿(HTML에 데이터가 삽입된 상태)을 서버로 부터 전달받습니다. 이 때문에 검색 로봇이 페이지를 크롤링하기에 적합합니다.

 

 

이를 보완하기 위해서 SPA 라이브러리들은 SSR 기능을 지원합니다. 이 부분은 제가 직접 해보지 않아서 설명하기 조심스럽네요. 추후 기회가 되면 다시 다루어 보도록 하겠습니다.

다만, SEO를 위해서 굳이 SSR을 사용하면 뭔가 장점이 상쇄되거나 설계상으로 복잡해지는 면이 있을 것 같아서 적절한 것인지는 모르겠습니다. 

 

아무래도 최근의 프론트엔드 라이브러리들은 단순한 웹사이트보다는 웹 애플리케이션에 사용하는 경우가 많으므로, 개인적으로는 소개 페이지 등의 검색에 노출되어야 하는 웹 페이지는 기존 SSR 방식으로 두고, 애플리케이션 구동 부분만 SPA로 운용해도 별 문제없지 않을까 하는 생각이 듭니다.

 

 

3. SPA의 진화

거창하게 진화라고 하긴 했지만 앞선 챕터에서 1과 2번의 문제점에 대한 대응 방식을 간략히 설명하고자 합니다.

 

Lazy-Loading Module

애플리케이션이 매우 방대하고 복잡한 경우, 한번에 모든 파일을 다 불러오면 초반에 매우 느리게 로딩될 수 있습니다. 모바일 앱의 경우라면야 처음 인스톨하는 경우에만 참으면 되지만, 웹에서는 약간 이야기가 다르겠죠? 사용자 경험 측면에서 유쾌한 현상은 아닐 것입니다.

 

앵귤러에서는 이를 보완하기 위해서 Lazy Loading (지연 로딩) 기능을 지원합니다.

처음에 모든 리소스를 받아오는 것이 아니라, 특정 URL에 진입할 때 그에 해당하는 조각을 받아올 수 있습니다. 지연 로딩을 활용하면 애플리케이션 초기 실행에 필요한 빌드 결과물 크기가 작아지기 때문에 앱 초기 실행 시간을 줄일 수 있습니다.

 

초기 로딩에 걸리는 시간을 라우팅 별로 분산시킨다는 의미로 보면 되겠습니다. 물론, 한번 로드된 부분은 더 이상 서버에서 받아오지 않습니다.

 

아래 그림은 앵귤러에서의 Lazy Loading에 대한 예제입니다. 최초의 페이지 (Home) 로드 후 "/orders", "/customers"로 이동하게 되면 해당 라우팅 페이지에 해당하는 파일 조각을 서버에 요청하여 받아오게 됩니다. 즉, 초기 로드 시에는 Root와 Home에 대한 code만 보유하고 있고 라우팅에 따라서 순차적으로 필요한 리소스를 로드를 하게 됩니다.

 

 

이 과정 자체를 사용자가 복잡하게 코딩할 필요는 없고, 약간의 설정만 해주면 프레임워크가 알아서 처리합니다.

 

사전 렌더링 (Pre-Rendering)

페이지 이동시 데이터 전송이 필요한 경우가 있습니다. SPA에서는 데이터가 없는 화면이 먼저 표시된 후, 데이터가 서버로부터 수신되면 화면을 다시 구성하게 됩니다. 이 경우 화면 전환이 그다지 보기 좋지 않을 수 도 있겠죠?

 

이를 가장 간단하게 극복하려면 단순하게 로딩 화면(로딩 스피너 등...)을 넣어주면 됩니다. 사용자야 좀 심심하겠지만...

요즘은 그보다 좀 더 우아하게 스켈레톤 화면을 표시하기도 합니다. 이를 스켈레톤 로더, 혹은 사전 렌더링이라고 부르기도 하며, 앱에서는 App Shell이라고 하는 것 같습니다.

 

사전 렌더링은 페이지의 AJAX 요청으로 데이터를 받아오는 동안 대략적인 골격 템플릿이 표시됩니다.

대략 아래와 같은 느낌이라고 보시면 될 것 같습니다.

 

이런 느낌? (https://ionicthemes.com/product/ionic5-full-starter-app)

 

 

아무래도 단순한 스피너 보다는 UI 측면에서 좀 더 고급스러워 보일 것 같습니다.

그냥 스피너 수준에서 만족하면 좋으련만...

하나 둘 이런거 넣기 시작하면 아무래도 비교되니 안 넣을 수가 없겠죠 -.-;

 

여러모로 개발자는 피곤합니다.

 

맺음말

최근에 여러 걸출한 프론트엔드 프레임워크들에 의해서 점점 SPA의 사용 빈도가 높아지고 있는 것 같습니다. SPA를 사용함으로써, 사용자의 UX가 향상되는 장점이 있습니다. 또한 서버의 구성이 API 위주로 단순화되고, 서버 개발자와 프론트엔드 개발자의 롤이 비교적 잘 구분되는 등 여러모로 좋은 점이 많은 것 같습니다.

 

SPA 활성화의 큰 걸림돌인 SEO 측면만 잘 해결된다면 더욱 활성화 될 수 있지 않을까 생각합니다.

물론 Angular Universal 등 SSR 방식을 일부 적용하는 방안도 있습니다. 하지만 보통 어떤 문제를 해결하기 위해서는 다른 무언가가 손해를 보는 경우가 많기 때문에 개인적으로는 검색엔진이 진화해서 SPA를 마음 놓고 사용할 수 있게 되면 좋겠습니다.

 

반응형