2017년 11월 11일 토요일

[Web] Vue.js + PWA + pre-render 프로젝트 틀 잡기 (feat. AWS S3를 이용한 웹호스팅)

Vue.js + PWA + pre-render 프로젝트 틀 잡기

(feat. AWS S3를 이용한 웹호스팅)



이번 포스팅에서는 [ Vue.js + PWA + pre-render ] 조합의 웹앱을 [ AWS S3 + CloudFront ] 조합에서 웹호스팅을 할 계획으로 Vue.js 프로젝트를 준비하는 과정을 정리해보려합니다.

0. 왜 이런 조합의 프로젝트를 준비하는가?

프로젝트를 준비하기에 앞서 제 스스로도 목적을 정리하고자 왜 위와 같은 조합을 만드는지 써보려합니다.
가볍게 의식의 흐름대로 써보자면 ㅎㅎ
Vue.js로 만든 웹페이지를 만들자!
=======================> Vue.js
그런데, 앱 같은 웹을 만들어서
우리는 웹을 만들지만 사용자는 앱을 사용하는 것처럼 느끼게 하자.
=======================> PWA(Progressive Web Apps)
이렇게 웹앱을 만들어서 AWS S3에서 웹호스팅하면,
서버 비용도 줄고 가용성도 좋으니깐, AWS S3에서 웹호스팅 하자.
=======================> AWS S3
아참! PWA를 위해서는 HTTPS를 지원해야겠다!
=======================> AWS CloudFront(with AWS ACM)
아...근데 일부 소개 페이지는 SEO도 되야겠는데..
서버가 없으니 SSR이 안되겠네..
그럼 SEO를 위한 페이지만 pre rendering을 하자!
=======================> pre-render

이런 의식의 흐름으로 [ Vue.js + PWA + pre-render ] & [ AWS S3 + CloudFront ] 조합이 탄생했습니다 ㅎㅎㅎㅎ


1. 프로젝트 시작

Vue.js webpack template 중에 PWA(Progressive Web Apps)를 위한 탬플릿이 이미 존재합니다.
아래 Github에서 해당 탬플릿을 시작할 수 있습니다.
https://github.com/vuejs-templates/pwa
해당 탬플릿의 원형은 vue webpack template이기 때문에 프로젝트 구조 등에 대한 문서는 아래 링크를 확인하면 좋습니다.
https://vuejs-templates.github.io/webpack
문서의 안내에 따라서 프로젝트를 시작할 폴더에서 아래의 명령으로 탬플릿을 다운로드 받습니다.
(단, vue-cli가 사전에 설치되어 있어야합니다.)
// 탬플릿 다운로드
$ vue init pwa my-project

// 프로젝트 폴더로 이동
$ cd my-project

// 의존 패키지 설치
$ npm install

// 개발 서버 시작
$ npm run dev
탬플릿을 다운로드 받을 때 프로젝트 설정을 위한 질문이 여러개 나오는데, 전부 디폴트로 설정해도 무방하고 조금 다른 설정을 해주고 싶은 것만 선택하시면 됩니다.
여기까지만으로도 벌써 Vue.js를 이용한 PWA 웹앱의 기반이 다져졌습니다!
여기서, 딱 두개만 수정을 하겠습니다.
(1) PWA 웹앱을 만들기 위한 manifest.json 파일에서 start_url을 아래와 같이 수정해줍니다.
// 
{
  "start_url": "/",
}
기본적으로는 /index.html로 설정되어 있는데 이것을 / 로 수정합니다.
왜냐하면, 본 포스팅에서는 PWA 웹앱을 AWS S3에 업로드하여 웹호스팅을 하고 AWS CloudFront와 연결하여 https 지원을 할 예정인데,
AWS에 PWA 웹앱을 연결하는 과정에서 이미 최초 진입 파일을 index.html로 설정하기 때문에 manifest.json에서는 시작 주소를 단지 루트(/) 경로만 표시해줘야 정상작동합니다.
(2) /build/service-worker-prod.js에서 service-worker.js 파일 위치를 수정합니다.
load 이벤트 리스너를 추가하는 부분에서 sevice-worker.js를 등록하는 코드가 아래처럼 있습니다.
navigator.serviceWorker.register('service-worker.js')
기존 처럼 되어 있으면 하위 path마다 각 path에서 service-worker.js를 찾게되고 당연히 파일이 없을거라서 등록에서 오류가 납니다.
그래서 아래처럼 파일 이름 앞에 슬래쉬를 붙여서 절대주소를 명시합니다.
navigator.serviceWorker.register('/service-worker.js')



2. 프로젝트 배포

2-1. AWS S3 + CloudFront 에 웹앱 배포

저는 AWS S3에 프로젝트를 배포하고 S3의 웹호스팅 기능으로 웹앱을 사용해 보려합니다.
AWS S3로 웹호스팅 하는 방법을 아래 링크를 참고하시면 좋습니다.
https://walkinpcm.blogspot.kr/2017/06/aws-aws-s3-static-website-hosting.html
링크의 내용에서는 HTTPS를 지원하는 내용까지 있는데,
PWA를 위해서는 웹앱이 HTTPS로 제공되어야 하기 때문에 일반적인 S3 Web Hosting 문서보다 위 문서가 도움이 되리라 생각됩니다.
배포할 파일들을 만들기 위해서 프로젝트 폴더에서 아래 명령으로 배포 파일을 준비합니다.
$ npm run build
빌드를 하고나면 프로젝트 폴더의 루트 경로에 dist 폴더가 생성됩니다.
dist폴더 내부의 모든 파일들을 S3에 업로드 하면됩니다.

2-2. SPA를 위한 AWS CloudFront 추가 설정.

SPA(Single Page Application)는 URL 주소가 변경되더라도 실제 다른 파일을 불러오는게 아니라 화면 요소만 바꿔줍니다.
그런 특징이 AWS S3 + CloudFront 조합에서는 문제점 하나를 야기합니다.
SPA에서 routing으로 URL 주소를 바꾼 상태에서(ex. '/def')
새로 고침을 하면 브라우저는 그 요청을 AWS CloudFront로 보내는데
CloudFront에는 '/def'에 대한 자원이 없어서 S3로 요청하게 되고 S3도 '/def'에 대한 자원은 없어서 결국에는 에러 페이지가 반환됩니다.
이 문제는 사용자 경험도 떨어 뜨릴 뿐 아니라 웹크롤러가 페이지를 읽지 못하는 문제이기도 합니다.
그래서 CloudFront의 에러페이지 설정에서 403, 404 에러가 발생하면 root path로 이동하도록 설정해줍니다.
설정 방법은 아래와 같습니다.
CloudFront에서 설정할 배포를 선택하고 Error Pages탭을 선탁합니다.
그리고 Create Custom Error Response를 선택합니다.

HTTP Error Code는 어떤 에러코드에 대해서 Error 처리를 할지 선택하는 것입니다.
403, 404 2개에 대해서 각각 추가해주셔야합니다.
Customize Error Response를 Yes로 선택하고,
Response Page Path에 /index.html을 기입합니다.
HTTP Response Code는 200: OK로 입력합니다.

에러코드 403, 404 대해서 위와같이 설정을 마치면 아래 처럼 에러 처리 항목 2개가 생겼을겁니다.

2-3. PWA 웹앱 확인

프로젝트 배포 파일들을 AWS S3에 업로드 하고난 뒤에 (HTTPS 까지 지원하게 한 뒤에), PWA 웹앱이라면 모바일 크롬에서 해당 페이지에 접속했을 때 하단에 홈 화면에 설치하겠냐는 배너가 나타나야 정상입니다.
하지만 설치 배너는 유저가 5분 동안 2번 방문해야 나타나는 조건이 있습니다.
개발과정에서는 그러한 조건을 충족시키기가 번거로우므로 웹앱에 브라우저로 접속했을 때 설치배너가 무조건 나타나게 해주는 설정을 합니다.
모바일 크롬에서 아래 주소를 주소창에 입력합니다.
chrome://flags/#bypass-app-banner-engagement-checks
그리고 해당 항목(bypass app banner engagement checks)를 사용으로 변경합니다.
변경하면 크롬을 다시 시작한다고 합니다. 다시 시작하고 다시 웹앱에 접속하면 설치배너가 나타납니다.
물론 설치배너를 통해서 설치 하지 않고, 크롬 메뉴에서 '홈 화면에 추가' 기능을 이용해도 똑같이 PWA 웹앱이 스마트폰에 설치되듯이 추가됩니다.


3. Pre render 플러그인 사용하기

위에서도 언급했듯이 Vue.js PWA 탬플릿은 원형이 Vue.js webpack 탬플릿이기 때문에 Vue.js webpack의 문서를 참고하면됩니다.
그리고 고맙게도 그 문서에는 Pre render에 대한 내용도 있습니다.
Vue.js에서 Pre render를 할 때는 prerender-spa-plugin를 추천합니다. Vue.js에서 공식적으로 추천하고 있습니다.
(문서: https://vuejs-templates.github.io/webpack/prerender.html)
(Github: https://github.com/chrisvfritz/prerender-spa-plugin )
사용방법은 무척 간단합니다.
(1) prerender-spa-plugin 설치
npm i prerender-spa-plugin --save-dev
(2) webpack.conf.js에 플러그인 설정
Vue.js PWA 탬플릿을 사용했다면 /build/webpack.prod.conf.js에 아래 코드들을 추가합니다.
먼저, 아래 require 문을 다른 어떤 require(또는 import)들 보다 상위에 작성합니다.
제일 상위에 위치하지 않으면 페이지가 정상적으로 표시되지 않습니다.
var PrerenderSpaPlugin = require('prerender-spa-plugin')
그리고, 아래 코드를 plugins 배열에 추가합니다.
new PrerenderSpaPlugin(
  // 컴파일된 파일들이 위치하는 폴더를 명시합니다.
  path.join(__dirname, '../dist'),
  // pre render를 할 path를 지정합니다. 콤마로 연결해서 여러 path를 기입할 수 있습니다.
  [ '/', '/abc' ]
)
(3) vue-router 의 mode를 history로 변경
prerender-spa-plugin을 사용하려면 router의 mode가 반드시 history여야합니다.
그러므로 아래와 같이 vue router의 mode를 history로 설정합니다.
const router = new VueRouter({
  mode: 'history',
  routes: [...]
})
Vue.js PWA 탬플릿을 사용한다면, /src/router/index.js에 mode를 추가하면 됩니다.
아래는 탬플릿의 원형에 mode만 추가한 것입니다.
import Vue from 'vue'
import Router from 'vue-router'
import Hello from '@/components/Hello'

Vue.use(Router)

export default new Router({
  mode: 'history',
  routes: [
    {
      path: '/',
      name: 'Hello',
      component: Hello
    }
  ]
})



여기까지 전부 하셨다면 Vue.js + PWA + pre render 조합의 웹앱의 기반을 잡으셨다고 할 수 있습니다.
그리고 그 웹앱을 AWS S3 + CloudFront를 이용해서 웹서버 없이 저렴하고 가용성 높게 배포 하게 되셨습니다.
혹시 참고하실 수 있으실까해서 이 포스팅을 준비하면서 연습해본 프로젝트 소스를 Github에 올려두었습니다.
아래 링크에서 참고하시면 도움이 되리라 생각됩니다.
(Github Repository 링크 : https://github.com/ChanMinPark/vue-pwa-prerender-s3-cloudfront )


이번 포스팅은 여기서 마치겠습니다.
혹여 내용이 틀리거나 수정 보완 해야하는 부분이 있다면 댓글로 남겨주세요.
감사히 받아들이고 수정, 보완하겠습니다.
감사합니다.


참고링크

댓글 2개:

  1. 와.. 앱 다 만들고 광고가 계속 안나오길래 콘솔을 보니 404 error라고 나오면서 페이지를 찾을 수 없어서 애드센스에서 해당 게시글에 광고를 개제할 수 없다고 나오더군요..

    첫번째로 해줘야할 일은 s3에서 정적 웹사이트 호스팅 항목에서 오류 문서를 index.html로 바꿔주는 것과

    그 다음은 본문에 글쓰신대로 다시 클라우드 프론트에서 설정해주는게 필요했네요 정말 감사합니다.

    답글삭제