-
[스압/데이터주의] 웹 최적화 방식 모음 - 4. 로드 후프로그래밍/Web 2021. 2. 14. 18:25
- [스압/데이터주의] 웹 최적화 방식 모음 - 0. 전반적 원칙과 원리
- [스압/데이터주의] 웹 최적화 방식 모음 - 1. 다운로드
- [스압/데이터주의] 웹 최적화 방식 모음 - 2. 파싱 및 렌더링 트리
- [스압/데이터주의] 웹 최적화 방식 모음 - 3. Layout 및 렌더링
- [스압/데이터주의] 웹 최적화 방식 모음 - 3.3 UX 트릭
- [스압/데이터주의] 웹 최적화 방식 모음 - 4. 로드 후(현재)
- [스압/데이터주의] 웹 최적화 방식 모음 - 5. 빌드
4. 로드 후
동일한 페이지, 다른 페이지
4.1 요청줄이기
다른 페이지로 넘어갈 때 요청량을 줄일 수 있는 여러 기법들이 존재한다. [Offline First]
4.1.1 클라이언트 저장소들 비교와 활용
개요
로드 후, 브라우저에 정보를 저장하는 것은 캐시로 사용해 서버에 요청을 줄이거나 낙관적 UI(Optimistic UI)를 구현할 때 커다란 도움이 될 수 있기 때문에 중요하다.
크로미움의 어플리케이션 탭에 들어가면 스토리지 항목 5개, 캐시 2개가 등장한다.
이를 Storage, DB, Cookie, Cache로 나누어 간단히 정리해보았다. [클라이언트 측의 저장소 살펴보기, 쿠키 vs 로컬스토리지: 차이점은 무엇일까?, 브라우저 쿠키(Cookie), 세션스토리지(Session Storage), 로컬스토리지(Local Storage), Working with quota on mobile browsers]
Storage
Web Storage는 클라이언트에 데이터를 저장하기 위해 만들어진 HTML5의 새로운 기능이다.
Key-Value 방식으로 이루어진다.
- Session Storage: 5MB, 같은 탭, 브라우저 종료 시 삭제
- Local Storage: 10MB, 모든 탭, 만료기간 없음
DB
웹어플리케이션을 만들도록 도와주는 브라우저 내부에서 사용할 수 있는 DB다. [Beyond HTML5: Database APIs and the Road to IndexedDB, IndexedDB 간단 정리하기]
IndexdDB: Key-Value 방식(JSON), 50MB, 모든 탭, 만료기간 없음
Web SQL(Deprecated): SQLite Wrapper, 50MB, 모든 탭, 만료기간 없음
Web SQL은 표준화를 시키기 어려워서 Deprecated 되었다.
This document was on the W3C Recommendation track but specification work has stopped. The specification reached an impasse: all interested implementors have used the same SQL backend (Sqlite), but we need multiple independent implementations to proceed along a standardisation path.
Cookie
Cookie[HTTP Cookie, Document.cookie, Javascript cookies]에는 저장 유형, 연결 유형에 따라 여러가지로 나뉘기도 하나 여기에서는 Session Cookie와 Persistent Cookie만 다룬다. [HTTP Cookie - Terminlogy, A JavaScript developer’s guide to browser cookies]
Key-Value 방식이지만 사용하기는 Storage보다 불편하다.
setCookie나 getCookie 함수를 만들어 사용하는 것이 좋다고 생각한다. [쿠키(Cookie) 저장 및 삭제 예제보기]
가장 큰 특징으로는 서버에 전송되어 저장될 수 있다.
Session Cookie: 4KB, 모든 탭, 브라우저 종료 시 삭제
Persistent Cookie: 4KB, 모든 탭, 만료시기가 되면 삭제
Cache
둘다 리소스를 캐싱하여 속도를 빠르게 만들고, 서버의 로드비용을 아껴준다. [Browser Cache vs HTML5 Application Cache, Browser Cache Vs HTML5 Application Cache]
- Cache Storage: 실제로 방문한 페이지만
- Application Cache(Deprecated): Manifest 사용, 100MB, 오프라인 브라우징
그러나 Application Cache는 Deprecated가 되었기 때문에 서비스워커 + 캐시 API로 마이그레이션을 해야 한다. [Application Cache를 사용하지 않는 사이트]
해결방안
일단 Deprecated된 Web SQL과 Application Cache는 사용하지 않는 것이 좋다.
Storage와 Application Cache는 메인 스레드를 차단하는 동기 API이므로, 대규모 데이터를 사용하려면 비동기적이고 웹워커에서 접근 가능한 IndexedDB를 사용하는 것이 좋다. [Progressive Web App용 오프라인 저장소]
또한 The structured clone algorithm을 사용하여 파일에서 JSON을 저장하거나 검색하는 것보다 낫다.
단, IndexedDB의 API가 상당히 복잡한편 이라는 것.
Basket.js를 사용하면 localStorage에 저장(캐싱)하는 것이 가능하다.
단, 요즘은 그냥 서비스워커 + 캐시로 사용하는 것이 좋다고 생각.
서비스 워커 사용법은 앞서 언급했으니 참고바란다.
4.1.2 쿠키 사용 줄이기
개요
HTTP 쿠키들(cookies)은 인증 및 개인화와 같은 다양한 이유로 사용된다.
쿠키들에 대한 정보는 서버에 전송되며 교환되므로 성능과 보안에 문제가 생길 수 있다.
쿠키들의 크기를 가능한 한 작게 유지하는 것은 사용자의 응답시간에 미치는 영향을 최소화하기 위해 매우 중요하다. [When the Cookie Crumbles]
해결방안
링크의 내용을 정리하면 다음과 같다.
- 불필요한 쿠키들을 제거
- 사용자의 응답시간에 미치는 영향을 최소화하기 위해 쿠키들의 크기를 가능한 한 작게 유지
- 다른 서브도메인들이 영향을 받지 않도록 적절한 도메인 수준(level)에서 쿠키설정을 주의
- 적절한 만료(Expires) 날짜를 설정
이른 만료날짜를 설정하거나 설정하지 않으면, 사용자의 응답시간 개선을 위해 쿠키를 즉시 제거 - 클라이언트에 정보를 저장해야 하는 경우 Storage나 IndexedDB 고려
3번째 항목을 조금 더 설명하자면 쿠키 free 도메인을 사용하자는 뜻이다.
정적 이미지와 같은 컨텐츠를 요청하며 쿠키를 함께 보내는 것은 서버에 아무런 영향을 끼치지 않는다.
쓸데없는 트래픽만 유발하는 행위.
만약 도메인이 www.example.org이라면, static.example.org에 정적 구성요소들을 호스팅 할 수 있다.
그러나 www.example.org가 아니라 최상위 도메인 example.org에 쿠키들을 설정했다면, static.example.org로의 모든 요청은 쿠키들을 포함한다.
이 경우, 완전히 새로운 도메인을 구입할 수 있고, 거기에 정적 구성요소들을 호스트 할 수 있다.
또한 이 도메인은 cookie-free 상태로 유지될 수 있다.
Yahoo는 yimg.com을 사용, YouTube는 ytimg.com을 사용하고 Amazon은 images-amazon.com 등을 사용한다.
Cookie-free 도메인에서 적정 구성요소들을 호스팅 하는 것의 또 다른 이점은 일부 proxies가 쿠키들과 함께 요청된 구성요소들을 cache하는 것을 거부할 수도 있다는 것이다.
같은 맥락으로, 홈페이지에 example.org 또는 www.example.org의 사용을 망설이고 있는 중이라면, 쿠키에 미치는 영향을 고려해야한다.
www를 생략하는 경우는 *.example.org의 쿠키들을 작성하는 것 밖에 할 수 없지만, 성능을 위해 www 서브도메인을 사용하고 그 서브도메인에 대한 쿠키들을 작성하는 것이 최선의 방법이다.
4.1.3 캐시 제어문 헤더 추가
개요
분류: server
이 규칙에는 두 가지 측면이 있다.
- 정적 components: 미래 설정 시점까지의 “Never expire” 정책 수행
- 동적 components: 조건적 요청의 브라우저를 수행하기 위한 적절한 Cache-Control 헤더의 사용
페이지를 처음 방문한 사용자는 여러 개의 HTTP 요청을해야 할 수도 있지만 캐시 제어문 헤더를 사용하면 해당 구성 요소를 캐시 가능하게 만들 수 있다.
이는 후속 페이지 로딩 시에서 불필요한 HTTP 요청 수 및 크기를 피하고 로딩속도를 빠르게 만든다.
이미지와 함께 가장 많이 사용하지만 스크립트, 스타일 시트를 포함한 모든 구성 요소 에 사용해야한다 .
캐시는 프록시나 CDN등에서 하는 Shared Cache, 브라우저에서 하는 Local Cache로 나눌수 있다.
웹 서버에서 Cache-Control, Expires, Pragma로 제어할 수 있는데 Cache-Control이나 Expires를 사용하는 것을 권장한다. [캐싱 작동 방식]
- Cache-Control
- 요청(Request), 응답(Response)
- 후에 설명할 Expires 헤더의 제한 사항 해결을 위해 HTTP 1.1에 도입됨
- 따라서 Expires와 충돌 시 우선한다.
- 헤더 종류
- no-store: 캐시하지 않고, 항상 다시 다운로드
- no-cache: 캐시하지만, 재검증을 위한 요청
- must-revalidate: 만료된 캐시만 재검증
- public | private: public은 프록시에 저장을 허용하고, private는 브라우저에만 저장을 허용
- max-age=<second>: 리소스가 유효하다고 판단하는 최대시간을 명시, 0이면 no-cache와 같음
예시
Cache-Control:public, max-age=31536000
- 만료(Expires)
- 응답(Response)
- HTTP 1.0에 도입
- <http-date> 값을 사용하며 max-age와 비슷하다.
예시
2020년 4월 15일까지 유효한 브라우저를 알리는 미래 시점의 Expires header이다.
Expires: Wed, 15 Apr 2020 20:00:00 GMT
- Pragma
- 요청(Request)
- HTTP 응답에서 명시되지 않았던 헤더라 Cache-Control의 신뢰할 만한 대체제가 아니며, 지원하지 않는 곳들도 많다.
- no-cache: Cache-Control의 no-cache와 같다.
예시
Pragma: no-cache
미래시점에 만료되는 헤더를 사용하면 사용자가 이미 사이트를 방문한 후에만 페이지뷰에 영향을 준다.
사용자가 처음으로 사이트를 방문하고 브라우저 캐시가 비어있는 경우 HTTP 요청 수에는 영향을 미치지 않는다는 말.
따라서 이 성능 개선의 영향은 사용자가 캐시된 페이지를 방문하는 빈도에 따라 다르다.
Yahoo!의 측정결과 캐시되어 있는 페이지 조회수는 75-85 %이었다.
미래시점의 헤더를 사용하면 사용자의 인터넷 연결을 통해 단일 바이트를 보내지 않고도 브라우저에서 캐시하고 후속 페이지뷰에서 재사용되는 구성 요소의 수를 늘릴 수 있다.
Ajax나 SPA에서도 캐싱을 적극적으로 활용하자.
해결방안
- 웹서버
aphache는 .htaccess 파일을 이용해 설정할 수 있다.[Caching Guide, mod_expires, Speed Up Sites with htaccess Caching]
<ifModule mod_expires.c> ExpiresActive On ExpiresDefault "access plus 1 month" # years | months | weeks | days | hours | minutes | seconds ExpiresByType image/gif "access 3 minutes" ExpiresByType image/png "access 3 minutes" ExpiresByType image/jpg "access 3 minutes" ExpiresByType text/css "access 3 minutes" </ifModule> <ifModule mod_headers.c> # YEAR <FilesMatch "\.(ico|gif|jpg|jpeg|png|flv|pdf)$"> Header set Cache-Control "public, max-age=29030400" </FilesMatch> # WEEK <FilesMatch "\.(js|css|swf)$"> Header set Cache-Control "public, max-age=604800" </FilesMatch> # 45 MIN <FilesMatch "\.(html|htm|txt)$"> Header set Cache-Control "max-age=2700" </FilesMatch> </ifModule>
nginx.conf를 다음처럼 설정해볼 수 있다 [A Guide to Caching with NGINX and NGINX Plus, Nginx 캐시 설정 및 캐시 만료일 기간 설정, Nginx Caching]
# cache.appcache, your document html and data location ~* \.(?:manifest|appcache|html?|xml|json)$ { expires -1; # access_log logs/static.log; # I don't usually include a static log } # Media: images, icons, video, audio, HTC location ~* \.(?:jpg|jpeg|png|gif|ico|gz|svg|svgz|ogg|mp4|webm|ogv|htc|cur)$ { expires 3M; access_log off; add_header Cache-Control "public", max-age=31536000; } # Source File location ~* \.(?:css|js)$ { expires 1M; access_log off; add_header Cache-Control "public"; } # Favicon location = /favicon.ico { expires max; access_log off; log_not_found off; } # Feed location ~* \.(?:rss|atom)$ { expires 1h; add_header Cache-Control "public"; }
sane-caching.nginx.conf(gist)에도 잘 나와있다.
php의 경우 다음과 같이 해볼 수 있는 모양이다. [header]
<?php header("Cache-Control: public, max-age=31536000"); //or header("Expires: Wed, 15 Apr 2020 20:00:00 GMT"); ?>
expressjs를 사용할 경우 web.dev의 문서를 참고할 수 있다.
- meta 태그 사용
과거 브라우저에서 사용하고, 현재는 잘 사용하지 않는 방식.
다음과 같이 사용할 수 있다. [A Dictionary of HTML META Tags]
<meta http-equiv="Cache-control" content="public, max-age=31536000"> <meta http-equiv="expires" content="Wed, 15 Apr 2020 20:00:00 GMT">
- 기한
구글이 제공하는 HTTP Caching 문서에서는 다음과 같이 되어 있다.
- HTML: no-cache
- CSS: public, max-age=31536000(1년)
- JS: private, max-age=315360000
- Image: max-age: 86400
JS파일에 개인정보가 없다면 public으로 해도 상관은 없다.
즉, 다음을 고려하여 설정해야 한다.
- 각 리소스별 최적의 캐시 수명
- 프록시 캐시 허용 여부
- 기타 주의사항
미래에 만료되는 헤더를 사용하는 경우 구성 요소가 변경 될 때마다 구성 요소의 파일 이름을 변경해야 한다.
이 단계를 빌드 프로세스의 일부로 만들 수 있다.
예를들어 EXAMPLE_2.0.6.js처럼 버전 번호, EXAMPLE_20200402.js처럼 날짜등을 파일 이름에 포함할 수 있다.
webpack의 경우 Caching 문서를 확인해보면 자동으로 생성해준다.[Hash vs chunkhash vs ContentHash]
4.1.4 유효성 검사
개요
분류: server
캐시가 유효하지 않으면(또는 만료되었으면) 다시 서버에 요청을 해야하는 것은 분명하다.
그런데 매번 새로운 파일을 다운받는 것은 비효율적이므로 서버의 파일이 동일한 것인지 유효성 검사를 하면 다운로드를 줄일 수 있다.
Cache-Control: must-revalidate이 명시되어 있거나 만료시간이 가까워도 검증은 필요하다.
유효성 검사 결과 수정되지 않았다면 요청한 리소스 본문대신 304 Not Modified를 보내며, 수정되었다면 200 OK와 함께 새로운 리소스 본문과 새롭게 대체할 유효성 값을 보낸다.
- ETag
ETag (Entity 태그)는 특정 버전의 구성 요소를 고유하게 식별하는 문자열으로 일종의 해시값이라 생각하면 쉽다.
"Entity"는 이미지, 스크립트, 스타일 시트 등 "Component(구성 요소)"라는 또 다른 단어.
ETag는 후에 설명할 Last-Modified(마지막 수정 일자)보다 유연한 엔터티를 검증하는 메커니즘을 제공한다.
유일한 형식 제한은 문자열을 이용하는 것이다.
Etag는 강한 검증, 약한 검증이 존재한다. [HTTP 조건부 요청]
강한 검증은 바이트 대 바이트로 동일한지 보장하는 것으로 MD5같은 해시를 이용한다.
반면 약한 검증은 유사한 경우 버전이 동일하다고 간주하는 것으로 캐시 성능 최적화에 유용하다.
약한 검증으로 쓸만한 것은 날짜 혹은 광고만 다른 페이지.
약한 검증을 하고 싶으면 W/를 앞에 붙이면 된다.
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4" ETag: W/"0815"
응답 받았을 때 Etag 값은 If-None-Match 헤더에 포함시켜 전송 후 유효성을 판단한다.
- Last-Modified
Last-Modified는 말 그대로 마지막으로 수정된 날짜를 말하며 Expires의 형식과 같다.
엄밀하지 않기 때문에 약한 검증용으로 사용할 수 있다.(강한 검증은 불가)
응답 받았을 때 Last-Modified 값은 If-Modified-Since 헤더에 포함시켜 전송 후 유효성을 판단한다.
Last-Modified와 Etags의 관계는 Expires와 Cache-Control과 비슷해서 동시에 존재하면 Etags를 우선한다.
- 정리
자, 그러면 처음부터 유효성을 검증하는 과정을 생각해봅시다.
먼저 Etag나 Last-Modified와 함께 온다.
HTTP/1.1 200 OK Last-Modified: Tue, 12 Dec 2006 03:03:59 GMT ETag: "10c24bc-4ab-457e1c1f" Content-Length: 12195
나중에 브라우저가 구성 요소의 유효성을 검사해야하는 경우 If-None-Match나 If-Modified-Since 헤더를 사용하여 ETag나 Last-Modified 값을 원래 서버로 다시 전달한다.
일치하면 304 예제 코드가 리턴되어 이 예제의 응답을 12195 바이트만큼 줄인다.
GET /i/yahoo.gif HTTP/1.1 Host: us.yimg.com If-Modified-Since: Tue, 12 Dec 2006 03:03:59 GMT If-None-Match: "10c24bc-4ab-457e1c1f" HTTP/1.1 304 Not Modified
요청과 응답의 관점으로 보자면 [더 빠른 웹을 위하여 - 웹 캐쉬(WEB CACHE)]
요청
- 유효성(Validation): If-Modified-Since(1.0), If-None-Match
- 신선도(Freshness): Pragma(1.0), Cache-Control(1.1)
응답
- 유효성(Validation): Last-Modified(1.0), Entity Tag(1.1)
- 신선도(Freshness): Expires(1.0), Cache-Control(1.1)
요청과 관련된 추가적인 내용은 강/약한 검증에서 소개했던 HTTP conditional requests에 잘 나와있다.
해결방법
아파치는 FileETag를, Nginx는 etag 지시어를 이용해 조절 가능하다.
단, 서버를 여러대 사용할 경우, etag 값은 달라질 수도 있는데 이 경우 Etag를 제거하거나 inode 값을 빼야 한다.
그렇다면 전체적인 캐싱/유효성 검증은 어떠한 방식으로 결정해야 하는가?
구글에서는 다음 그림을 소개한다.
페이스북의 This browser tweak saved 60% of requests to Facebook를 읽어보아도 좋다.
4.1.5 AJAX/Websocket이나 SPA사용
개요
분류: Content
AJAX는 필요한 내용만 웹서버에 요청해 처리할 수 있다. [Ajax, 데이터전송]
따라서 AJAX를 사용하면 동일한 도메인 내의 페이지를 이동할 때 불필요한 부분을 다운받지 않아 성능에 유리하다.
가 라이브러리로 유명하다.
Websocket
이때 Websocket을 사용한다면 데이터 통신을 더 줄일 수 있다. [HTML5 WebSocket(웹 소켓) 기본 예제 및 설명, Moving Data over the Web: AJAX vs. WebSockets vs. Webhooks]
말그대로 소켓의 역할을 해서 실시간 업데이트에 유리하다.
위 그림에 보이다시피 매번 HTTP 헤더가 필요한 AJAX와 달리 한번만 설정하면 바로바로 통신할 수 있다.
웹소켓의 라이브러리는
가 유명하다.
그런데 항상 빠른 것은 아니다. [Speed dilemma of Node.js: Which do AJAX and Socket.IO choose?, HTTP vs Websockets: A performance comparison]
웹소켓은 연결을 설정하는데 약 190ms정도의 시간이 필요하기 때문.
즉,
- 적은 수의 데이터를 가끔씩 업데이트: AJAX
- 많은 수의 데이터를 빈번히 업데이트: 웹소켓
이 좋은 방안이라 할 수 있다.
+.
Server-Sent Event도 고려해볼법만 하다. [WebSockets 대신 Server-Sent Events 사용하기]
웹 소켓은 압축, 멀티플렉싱등을 지원하지 않는다는 문제가 있다.
해결방안
AJAX의 문제와 해결
AJAX를 사용시 가장 큰 문제는 주소가 업데이트되지 않아 뒤로가기가 되지 않는다는 점이다. [Ajax를 사용할 때 웹브라우저 "뒤로 가기"의 구현, page-back-example]
이때 해결할 수 있는 방법은 HTML5 History의 pushState()를 사용하는 것이다.
SPA와 렌더링
이러한 AJAX의 장점을 이용해 페이지를 빠르고 부드럽게 렌더링하는 패러다임이 등장했으니 React, Vue, Angular등으로 유명한 SPA다. [Single Page Application vs Multi Page Application, SPA와 SPA 라우팅 원리, Single Page Application & Routing, 서버사이드 랜더링과 SPA 라우팅의 동작원리]
보다시피 AJAX의 통신방법을 잘 써먹고 있다.
그러나 웹페이지 렌더링을 자바스크립트를 이용해 처리해 초기속도가 느리다는 점, AJAX의 특징등 때문에 다양한 렌더링 방식이 등장하게 되었다. [웹에서의 렌더링, 웹렌더링]
가급적 링크의 내용을 참고바람. 절대 시간이 아까운 내용은 없다.
- Server Rendering: 요청한 페이지의 전체 HTML을 서버에서 생성해 로드
- Static Rendering: 빌드시 HTML을 생성하여 배포
- Client-Side Rendering: 브라우저에서 자바스크립트로 페이지를 렌더링
- SSR + Rehydration: 캐시성이 높은 컨텐츠에만 SSR을 적용
정리된 이미지.
장점은 대부분 이해갈테니 간단히 설명해보자.
- Full CSR: 서버에서 JS와 CSS를 뿌리면, SPA이 자동으로 만들어진다. 따라서 처음에 HTML과 컨텐츠는 포함이 되어있지 않고, FCP(최초 컨텐츠 포함된 페인트)가 느려지게 된다.
- CSR with Prerendering: 나아지긴 했지만, 여전히 FCP에서 문제가 있다
- SSR with hydration: 밑에 나오는 SSR의 단점에 이벤트 핸들러가 붙는 시간까지 합쳐야 해서 TTI(상호작용까지의 시간)까지 추가된다.
- Static SSR: 빌드 타임에 생성하므로 매우 빠르다. 그러나 동적이지 않다.
- Server Rendering: 서버에서 생성하는 과정이 필요하므로, TTFB(최초 바이트까지 시간)에서 문제가 있다.
보다시피 매우 다양한 트레이드오프가 있는 것을 알 수 있다.
일단 Static SSR이 성능만 놓고 보면 최적으로 보인다.
그러나 모두 알다시피 페이지들을 미리 만들어야 하는데 구글의 검색정보 페이지처럼 동적인 요소(구현 문제)가 많거나 사이트의 규모가 매우 큰(서버측 리소스 낭비) 경우는 문제가 있을수있다.
첫번째는 Headless CMS로 어느정도 극복가능하구 [CMS의 새로운 형태, Headless CMS, 헤드리스 CMS]
두번째는 Gatsby의 Deferred Static Generation, Next.js의 Incremental Static Regeneration으로 해결가능하다.
어쨌든 성능측면에서 장점이 많은지 최근 정적인 방법인 JAM Stack!! 이 각광을 받고 있다. [조금만 신경써서 초기 렌더링 빠르게 하기 (feat. JAM Stack)]
그러나 서버 사이드 CMS, 백엔드와 분리되지 않은 서비스, isomorphic rendering으로 운영되는 서비스에서는 사용하기 어렵다고 한다.
물론 가장 좋은 방법은 프로그래시브 렌더링이 되도록 만드는 것.
리엑트 18이 이를 가능하게 만든다. [New Suspense SSR Architecture in React 18, React 18 변경점]
이른바 Selective Hydration라 부르는 물건.
모든 자바스크립트가 로드될 때까지 기다리지 않고 각각의 컴포넌트를 수화할 수 있을뿐만 아니라, 사용자가 상호작용 하는 컴포넌트를 우선적으로 조기에 수화한다!!
렌더링 개선법
CSR의 효율을 최대한 높히기 위해 PRPL(Push Render Pre-Cache Lazy-load) 패턴이 생겼고(물론 우리가 위에서 다루었던 최적화 방법들),
서비스 워커를 사용하면 초기화와 자바스크립트를 사용하지 않은 Navigation에서 스트리밍을 할 수 있다.
아참, MPA의 경우 Streaming Composite Response를 통해 정적 헤더와 푸터등을 캐싱할 수 있다.
MPA에서 SPA처럼
MPA도 AJAX를 사용하면 SPA처럼 렌더링할 수 있지 않을까 싶어 찾아보게 되었다.
그러다 굉장히 흥미로운 구현체들을 보게되었는데 전체 페이지로드를 발생시키지 않고, AJAX를 이용해 페이지의 바뀐부분만 업데이트시키는 방법이었다. [pjax를 이용한 극적인 웹페이지 속도 향상, How We Migrated To Turbolinks Without Breaking Javascript]
쉽게 설명하면
- head: 병합
- body: 교체
와 같은 원리로 사용된다.
대표적인 라이브러리로는
가 있으며 성능향상이 충분하다.
다음은 PJAX 아이디어를 기반한 라이브러리들.
얼마나 효과가 있을지 모르겠으나 기존 SPA와 함께 사용할 수도 있는듯 하다.
+.
재미난 프로젝트를 발견했다.
Hotwire : HTML Over The Wire(긱뉴스)
pjax를 활용했는지 확실치 않지만..
유튜브에서 만든 SPF(Structured Page Fragments)라는 프레임워크도 재미있어 보인다.
4.1.6 AJAX 요청에 GET 사용
개요
분류: server
야후! 메일팀은 XMLHttpRequest의 POST를 사용할 때 브라우저에서 헤더를 먼저 보낸 다음 데이터를 보내는 2 단계 프로세스로 구현 한다는 것을 알게되었다 .따라서 쿠키가 많지 않은 경우 하나의 TCP 패킷 만 보내면 POST보다 GET을 사용하는 것이 가장 좋다.
IE 기준 최대 URL 길이는 2K이므로 2K 이상의 데이터를 보내면 GET을 사용하지 못할 수 있다.
다음은 일반적으로 말하는 GET과 POST의 장단점. [GET vs. POST]
- GET
장점
- 고정적인 주소 및 링크를 사용하기 때문에 캐싱 가능
- Content-Type이 필요 없으므로(body가 없음) 가벼움, 야후 메일팀이 말한것
단점
- 길이 제한
- 주소창에 쿼리 스트링이 보여짐
- POST
장점
- 객체를 보낼 수 있음
단점
- 서버로 보내기 전에 인코딩, 전송 후 디코딩 작업
- 캐싱이 힘들고, 데이터를 2단계에 걸쳐 보냄
- 기타
흥미로운 부작용은 포스팅된 데이터가 없는 POST는 GET처럼 동작한다는 것이다.
HTTP 사양을 기반으로 살펴보면 GET은 fetch(요청)할 때 사용, POST는 수행할 때 사용한다.
CRUD(Create, Read, Update, Delete) 중 Read할 때만 fetch를 하는 개념이므로 GET을 사용하는 것이 맞다는 것이다.
그런데 개발자들이 구분하지 않고 사용하여 미리 링크를 클릭해(GET) 성능 향상을 하려 시도했던 구글 Accelerator(전버전)가 실패한 적이 있다나.
4.2 기타
4.2.1 로드 전 구성 요소
개요
분류: content
프리로드는 포스트로드와 반대되는 것처럼 보이지만 실제로는 다른 목표가 있다. [The Case for Auto-Preloading: The Anatomy of Battle-Tested WPO Treatment, How To Make Users Think Your App Loa보다 ds Faster, Faster Loading Times with Prefetch, Preload and Prerender, Best-ish Practices for Dynamically Prefetching & Prerendering Pages with JavaScript]
컴포넌트를 사전로드하면 브라우저가 유휴 상태 인 시간을 활용하고 나중에 필요한 컴포넌트, 이미지, 스타일 및 스크립트를 요청할 수 있다.
이렇게 하면 사용자가 다음 페이지를 방문 할 때 이미 캐시에 대부분의 구성 요소가있을 수 있으며, 페이지가 사용자에게 훨씬 빠르게 로드된다.
반대로 업로드도 가능하다.
해결방법
- 유형
몇 가지 유형의 사전로드가 있다.
무조건
onload가 발생하자마자 추가 구성 요소를 가져온다.
스프라이트 이미지가 onload후 요청되는 방법에 대한 예는 google.com이 있다.다운받은 스프라이트 이미지는 google.com 홈페이지에는 필요하지 않지만 연속 검색 결과 페이지에는 필요하다.
조건부
사용자 조치를 기반으로 사용자가 다음으로 향하는 위치를 정교하게 추측하고 그에 따라 사전로드한다.
search.yahoo.com의 검색창에 입력을 시작 후 몇 가지 추가 구성 요소를 요청하는 방법을 볼 수 있다.
업로드도 가능하다는 예를 보이기 위해 설명하자면,
인스타그램은 사용자가 공유할 사진을 고르면 미리 업로드를 백그라운드로 진행하기도 한다.
예상
redesign을 시작하기 전에 미리로드한다는 아이디어.
"새 사이트는 멋지지만 이전보다 느리다."라는 말은 redesign 후 흔히 들을 수 있다.
문제는 사용자가 전체 캐시를 사용하여 이전 사이트를 방문하고 있었지만, 새 사이트는 항상 빈 캐시 환경이라는 것이다.
redesign을 시작하기 전에 일부 구성 요소를 사전로드하여 이 부작용을 완화 할 수 있다.
기존 사이트는 브라우저가 유휴 상태인 시간을 사용하고 새 사이트에서 사용될 이미지 및 스크립트를 요청할 수 있다.
인스타그램의 경우 컨텐츠를 순서대로가 아니라 중요도로 예측하여 다운받는다.
SNS이므로 많은 사용자가 좋아하는 컨텐츠에 들어갈 확률이 관심을 적게 받는 컨텐츠보다 높기 때문.
사실 무조건, 조건부, 예상이든 모두 사용자가 사용할 것이라 '예측'하기 때문에 로드한다.
- 기술
리소스 힌트를 사용하면 미리 캐싱을 할 수 있다. [Prefetching, preloading, prebrowsing, Prebrowsing, Best-ish Practices for Dynamically Prefetching & Prerendering Pages with JavaScript, Resource Hint, Web Developer's Guide to Prerendering in Chrome]
Prerender(실험적)
이 중 아직 언급하지 않았던 prerender를 다루어보려 한다.
prefetch와 같이 다음탐색에 요구될 수 있는 자원에 대해 더 빠른 응답에 대한 준비, 실행을 하도록 만든다.
차이점이라면 브라우저가 백그라운드에서 URL을 가져오고 렌더링하여 즉각적으로 보여줄 수 있다. [Document.visibilityState]
문제는 페이지를 통째로 다운받기 때문에 리소스 사용량이 많다는 것.
따라서 거의 무조건 넘어갈 것이라 예상할 때만 이 힌트를 사용해야 한다.
정리
- 예측되는 도메인: dns-prefetch, preconnect
- 예측되는 리소스: prefetch
- 예측되는 페이지: prerender
- 또 다른 활용방법
위에서 나온 예시들 말고 한가지 흥미로운 예측방법을 소개하려한다. [Prerender on hover?]
화면에 링크가 보이거나 마우스를 링크에 올리면 들어갈 확률이 높기 때문에 미리로드를 하자는 것이다.
이를 도와주는 라이브러리가 존재한다. [Quicklink vs Instant.page vs Flying Pages – Why I built Flying Pages]
- Quicklink: Viewport 기반
- Instant.page: Hover 기반
- Flying Pages: Viewport, Hover 둘다
- Guess.js: 구글 애널리틱스등의 분석기를 통해 예측
Hover 기반에서 문제는 호버 후 서버응답시간(약 300ms)가 누르는 시간보다 짧을 수 있다는 것이다.
이럴 때는
처럼 마우스 움직임을 예측하도록 도와주는 라이브러리를 쓸 수 있다.
한편, 지금까지와 반대로 hoverintent(jQuery plugin)처럼 너무 민감하지 않게 만드는 방법이 필요할 수도 있다. [Faking Hover Intent with JavaScript]
사전에 로드하는 것은 리소스를 소모하는 작업이기 때문.
빠른 페이지 렌더링과 관련된 기술과 융합해 사용할 수도 있다.
댓글