NUXT3 Project DevelopmentLog

image-1

메인페이지 시연영상. 해당하는 요일만 효율적으로 GET요청

image-1

새로고침시 변경되는 메인영상

image-1

방송선택시 변경되는 메인영상

개발 두번째 프로젝트. 모티브가 된 onsen.com 처럼 메인 좌측에 영상스트림, 우측에 공지 등의 캐러셀을 배치했다. 요일별로 방송이 있고, 해당 요일을 클릭하면 방송리스트가 나열된다. 예전이었다면 생각없이 모든데이터 불러와서 프론트에서 요일별로 나누는 효율없는 방식을 사용했겠지만 이번엔 쿼리스트링으로 효율적으로 데이터를 요청하는 방식을 사용했다. 메인 영상스트림은 초기에 랜덤으로 1개 뽑아와서 노출하고있다. 추후 사용자가 재생할 영상을 선택하면 해당영상으로 대체 된다.

image-2

로그인 실패 시 횟수제한, 성공 후 refresh, access 토큰확인

image-2

Google OAuth 로그인 시연영상

로그인, 로그아웃기능은 JWT를 사용했고 로그인 시 accessToken은 localstorage에, refreshToken은 쿠키에 저장했다. Google OAuth도 지원한다. Vue+SpringBoot와 개발진행과정은 동일하지만 리다이렉트 페이지 설정. 특히 Docker를 사용하거나 production 환경에서 구글 클라우드 콘솔과의 엔드포인트, URI에 차질이 있었기에 주의가 필요하다고 생각했다. 하지만 이 역시 큰그림은 이해했기에 결과적으로 구현하는데엔 시간문제였다.

image-3

방송리스트 시연영상. 모든 방송엔 각자 에피소드 추가하는기능이 있고, 수정or삭제 버튼 클릭 시 해당방송의 고유번호에 따라 데이터를 바인딩. 혹은 삭제.

image-3

방송(콘텐츠)추가 시연영상1. 검색기능도 구현했다.

image-3

방송(콘텐츠)추가 시연영상2. 실제 방송을 추가하여 디테일화면에서 방송 진행자 정보도 팝업으로 확인가능하다.

image-3

방송(콘텐츠)수정 삭제 영상. 여담으로 개발자도구 네트워크탭에 만료된 토큰 갱신하는 refresh-Token 엔드포인트도 정상적으로 작동중

방송리스트엔 모든 방송목록을 나열한다. 어드민페이지에서 방송, 진행자를 추가할 수 있고 방송 추가시에 DB에 등록되어있는 진행자 명단을 나열, 빠른검색기능도 구현했다. 물론 추가, 수정기능은 당연하게도 중복되는 코드가 있었기에 FormComponent로 컴포넌트화 했고 필요한 fields를 props전송, 수정기능의 경우 해당 방송의 _id에 해당하는 내용을 DB에서 불러와 바인딩했다 이 때 v-dialog 등 적극적으로 Vuetify를 활용해서 모달, 검색자동완성 등의 기능을 구현했다.
image-3

image-4

클라이언트(프런트엔드) 미들웨어. 단순하게 유저정보만을 확인한다

image-4

서버(백엔드) 미들웨어 예시.(상세코드 GitHub참조) JWT를 확인해보고 토큰이 없거나 유효하지않으면 401, 403에러를 낸다

NUXT에선 접근권한을 클라이언트, 서버미들웨어를 각각 구현할 수 있는데, 클라이언트 미들웨어에선 단순하게 유저정보를 확인해서 접근 제한할 컴포넌트에서 간단하게 미들웨어 등록하기만하면 사용가능하다. 서버사이드에선 체크할 엔드포인트를 미들웨어 코드에 적용하면 된다.독립적으로 작용하기에 보안상 중요한 api요청 등은 서버에서, 간단하게 접근만 막고싶으면 클라이언트 미들웨어에서 걸러내는 식으로 구현할 수 있었다. 클라이언트 사이드에서 라우터이동, alert등의 안내로 UX도 개선할 수 있다고 느꼈다.

image-5

운영자페이지 유저정보 변경 시연. 프리미엄/일반회원(role) 변경, 프리미엄 기간 등을 변경하면 좋을 것 같다.

image-5

배너관리 시연

image-5

멤버목록 검색 코드

운영자페이지는 앞서 미들웨어로 접근제어 하고 운영자 전용 layout으로 콘텐츠 추가 진행자 추가 등 운영자 전용 페이지 목록을 설정했다. 멤버목록의 검색기능은 쿼리파라미터로 검색어를 입력할때마다 API요청해서 해당하는 데이터만 바인딩했다. 이 기능을 구현하던 도중 단어 하나하나 입력할때마다 API를 요청하는 비효율성과 몽고DB에서 "자음"을 검색했을때 데이터를 찾지 못하는 오류등이 발생 했는데, 0.5초의 딜레이를 줘서 API요청을 하거나, MongoDB에서는 자음을 호환 유니코드로 저장되거나 하는 경우가있어서 매핑한다거나 하는 방식도 알게되었고 해결했다. 배너관리는 추가항목과 분리했고 활성화, 비활성화 상태를 DB에 저장하고 유저가 토글형식으로 손쉽게 변경할 수 있는 UX를 구현했다. 개선점이 있다면 상단배너가 뭐 어디에 위치한건지 슬라이드는 어디에 위치해있는지를 유저에게 쉽게 파악할 수 있게 해주는 네비이게이션이든 모달이든 UX개선이 필요할거같다.
image-5

image-6

FormComponent안에 FileUploadComponent를 자식 컴포넌트로 추가한다

image-6

FileUploadComponent의 handleFileChange함수. uploadToS3함수를 발동하고, 부모컴포넌트에 발급받은 PresignedURL을 전달한다

image-6

composable/userS3Upload.ts의 uploadToS3함수. file과 temp를 파라미터로 받고 파일사이즈같은것도 검증. api/upload로 API요청한다.

image-6

api/upload 엔드포인트의코드. S3관련 환경변수도 까보고 디코딩 등의 작업도 수행하고 presiginedURL을 요청한다.

image-6

다시 uploadToS3함수로 돌아와서 API요청이 성공했으면 uploadedUrl 변수에 해당 URL을 담는다.

image-6

FileUploadComponent의 comfirmFile함수. 여기서도 한번 더 uploadToS3함수를 실행하는데 이번엔 두번째 파라미터값(temp)이 false다 임시파일이아닌 확정적인 파일이므로.

image-6

부모 컴포넌트인 FormComponent로 돌아와서 각 FileUploadComponent를 사용한 필드마다 파일업로드를 확정한다. 하나라도 실패하면 폼이 불안정할 수 있기때문에 Promise.all로 모두 성공했을때만 폼을 제출하게 유도.

Vue+Spring 프로젝트와는 다르게 S3업로드를 구현했다. 1. 우선 FormComponent의 자식컴포넌트로 파일업로드 관련 작업을 처리하는 FileUploadComponent를 구현했다. 2. FileUploadComponent에서는 먼저 파일 업로드 시 uploadToS3 함수를 사용하여 S3 PresignedURL를 요청한다. (여기서 이 발급받은 URL은 임시로 사용한다. -> 파일업로드만 하고 글저장 안하는 악질유저 방지) 3. uploadToS3에서는 파라미터값으로 파일, temp(임시인지 아닌지 확인)를 받고 /api/upload로 api요청을 한다. 해당 API에선 S3 액세스,시크릿키 검증과 temp파일 여부에따라 폴더구조를 결정하고 presignedURL을 발급받는다. 4. 발급된 URL은 uploadedURL이라는 변수에 담고, emit함수로 부모컴포넌트에 전파한다. [자식(FileUploadComponent) -> 부모(FormComponent)에게 이 URL 받아왔어요~ 한다고 알려줌] 5. 파일이 만족스러워서 폼 전송을 하면 FileUploadComponent에서 confirmFile함수를 발동시켜서 S3에 파일업로드를 확정시키고, DB에도 확정된 URL을 저장한다.
image-6

image-7

GoogleKeep에 정리한 배포과정 1

image-7

GoogleKeep에 정리한 배포과정 2

image-7

GoogleKeep에 정리한 배포과정 3

image-7

GoogleKeep에 정리한 배포과정 4

최초엔 Docker와 EC2와 Github Action을 사용하여 CI/CD 를 구축하고자 했는데 NUXT프로젝트는 배포과정에서 자꾸 무한빌드 현상이 발생했다. 오류메세지가 뜨지않고 EC2에서 빌드자체가 되질 않아서 상당히 의문이었다. 결국 t2.micro의 메모리부족임을 깨닫고 해결방법을 탐색했고 결과적으로 로컬에서 프로젝트를 빌드 후 배포용 docker파일을 만들고 -> tar.gz파일로 압축 -> scip -id 명령어로 EC2인스턴스에 전송 -> docker load로 이미지를 로드 -> EC2인스턴스에서 docker-compose 를 작성하고 실행하는 방식으로 배포를 진행했다. 이 배포방법 역시 너무 제한적이고 수정할 때 마다 매번 새로 빌드, tar파일 전송등 제약이 너무너무너무 많기때문에 좀 더 나은 방법을 찾아야겠다고 생각했지만 사실 t1.micro의 메모리제한이 근본적인 문제라 더이상 나은 방법이 있을까? 싶기도 한다. 다른 배포 플랫폼을 찾는 방식도 당연히 선택할 수 있었지만, 현재 내 개발레벨에선 해결능력을 키우기 위해 문제에 봉착했을 때 몸 비틀어가면서 해답을 도출 하는게 결과적으로 피가되고 살이 될 것이라고 생각한다
image-7

image-8

기존의 onMounted와 SSR에 맞춰진 useAsyncData 사용 코드 비교

image-8

이전과 같이 정상적으로 렌더링 되고있다.

image-8

SEO에 필요한 NUXT만의 meta등록. script태그내에 정말 간편하게 등록할 수 있다.

SSR, SEO기능도 신경썼는데 사실 처음엔 NUXT 페이지 라우팅 자체가 SSR을 지원하기떄문에 자동으로 모든게 SEO에 최적화되어있다고 생각했다. 하지만 다른프로젝트나 공부를 진행하면서 Vue3 onMounted함수가 CSR이라는것에 대해 알게되어서 내 프로젝트도 수정이 필요하다고 생각했다. 우선 onMounted를 제거하고 컴포넌트 초기 연결시 한번 발동되는 useAsyncData를 사용하여 바인딩하는 식으로 수정했다. 이 NUXT기본 제공함수를 사용함으로써 먼저 HTML이 서버측에서 렌더링되어 SSR SEO를 구현할 수 있다. SEO의 핵심 전제조건은 1. JS실행없이도 콘텐츠가 보여야 한다는것 즉 서버에서 미리 페이지 만들어서 보여줘야한다는것(SSR) 2. meta태그 3. 외부링크 or 검색엔진 or 구글 서치콘솔에 등록되어야 할 것 4. robots.txt -> 크롤러가 접근 가능한 페이지 설정이 되어있어야한다는 것 이 네가지임을 명심해야겠다. 요즘 웹페이지 사용은 검색이 핵심이다 최대한 검색으로 노출되어, 유저들에게 많이 보여지는게 모던웹개발의 메타? 유행이자 웹개발에 돈 쓰는 이유라고 생각한다. CSR은 예쁘게 보이는곳, UX상향 시키고 싶은곳에 쓰자