이번 프로젝트는 제가 발표를 진행했고 해당 내용은 유튜브에 업로드했습니다. 관심 있으신 분들은 아래 링크 참고하셔서 시청 부탁드리겠습니다.
이번 프로젝트는 프론트엔드 프로젝트다보니 화면을 어떻게 설계했는지와 관련하여 짧게 소개해드리겠습니다.
우선 이번 프로젝트에서 사용한 기술스택은 다음과 같습니다.
기술스택
| 구분 | 사용한 기술 |
|---|---|
| 💻 Backend | Java, Gradle, SpringBoot, Spring Security, JPA, MyBatis, JWT, JUnit5, Swagger |
| 🎨 Frontend | HTML5, CSS3, JavaScript, Vue.js, Bootstrap, Pinia |
| 📦 Database | MariaDB, Amazon S3 |
| 🛠️ Tool | Git, GitHub, Postman, Notion, Figma, Miro, Google Sheets, Discord |
발표 영상을 보시면 아시겠지만 저희 링크업은 기획부터 화면설계, 그리고 구현까지 프론트엔드 개발에 필요한 모든 단계들을 설계하고 실행했습니다.
다음은 저희가 피그마 툴을 사용해서 직접 디자인하고 설계한 화면 설계서입니다.
피그마 화면 설계서

화면 설계 기간을 1주 정도로 잡았는데 저희가 디자인해야 하는 페이지는 70 페이지가 넘었습니다. 그래서 저희는 백엔드 프로젝트에서 담당한 도메인별 페이지를 제작하게 되었고 최대한 효율적이고 유지보수성이 높으면서 디자인 통일성을 줄 수 있는 컴포넌트를 설계했습니다.
컴포넌트 설계


또한 디자인을 각자 진행하다보니 서로 사용하는 색이 다르고 요소와 요소간의 여백이나 사용하는 폰트나 둥글기나 그림자 같은것이 다르다는 것을 깨닫고 이러한 디자인과 관련된 컨벤션을 정리할 필요성을 깨닫고 팀원분들이 보고 참고할 수 있도록 디자인 시스템을 설계했고 그 결과물은 다음과 같습니다. 피그마 스타일에도 적용하여 페이지 제작시에 필요한 색상이나 폰트들을 사용할 수 있도록 설정도 하였습니다.
디자인 시스템 설계




저는 원래 미디어커뮤니케이션 전공하면서 프론트엔드 개발을 공부했었고 이전 기획 회사에서 일했을 때 프로모션 페이지를 제작하고 배포했던 경험이 있기 때문에 시도해보고 싶었던 것이 많았는데 저번 백엔드 프로젝트와 동일하게 역시 해보고 싶었던 것을 모두 해내지 못해 아쉬운 마음이 큽니다.
우선 저는 다음과 같은 것들을 해보고 싶었습니다.
위에서 제가 이번 프로젝트에서 진행해본 것은 디자인 시스템 및 컴포넌트를 설계해보거나 예외 처리 및 로그인 상태 유지, 무한 스크롤 등만 적용해보고 그 외에는 시도해보지 못했습니다.
제가 우연찮게 네이버 테크 콘서트를 보면서 성능을 개선하는 것에 대한 큰 갈망을 느끼면서 이번 프로젝트에 꼭 적용해보고 싶었는데 시도해보지 못했다는 것에 매우 큰 아쉬움을 느끼고 SPA의 문제점 중 하나인 SEO 개선에 대하여 시멘틱 태그를 사용하거나 접근성을 높이는 등의 시도는 했지만 결국에는 가장 중요한 페이지별 메타 정보를 설정하지 못해 이것역시 너무나 아쉬움이 큽니다.
다음 데브옵스 프로젝트에선 해당 내용들을 적용해볼 시간은 없겠지만 꼭 최종때는 적용해 볼 것을 다짐하며 이번 아쉬움은 다음의 성공을 위한 발판이라고 생각하며 아쉬운 마음을 가라앉치고 다음은 제가 무엇을 했는지에 대해 작성해보고자 합니다.
우선 제가 이번 프로젝트에서 한 것에 대해 정리한 것은 다음과 같습니다.
우선 저희 프로젝트 구조는 다음과 같습니다.
프로젝트 구조
📦 LinkUp
├── 📁 linkup.src
│ ├── 📁 api
│ │ ├── 📄 admin.js
│ │ ├── 📄 axios.js
│ │ ├── 📄 user.js
│ │ ├── 📄 place.js
│ │ ├── 📄 community.js
│ │ └── 📄 meeting.js
│ │
│ ├── 📁 assets
│ │ ├── 📁 css
│ │ ├── 📁 icons
│ │ └── 📁 images.js
│ │
│ ├── 📁 components
│ │ ├── 📁 command
│ │ └── 📁 layout
│ │
│ ├── 📁 composables
│ │ ├── 📄 useLoadingBar.js
│ │ └── 📄 usePaginaition.js
│ ├── 📁 features
│ │ ├── 📁 admin
│ │ ├── 📁 auth
│ │ ├── 📁 community
│ │ ├── 📁 meeting
│ │ ├── 📁 place
│ │ ├── 📁 point
│ │ └── 📁 user
│ │ │ ├── 📁 views
│ │ │ ├── 📄 router.js
│ │ │ └── 📁 components
│ ├── 📁 router
│ │ ├── 📄 index.js
│ ├── 📁 stores
│ │ ├── 📄 auth.js
│ ├── 📁 util
│ │ ├── 📄 toast.js
│ ├── 📁 tokens
│ │ └── 📄 design-tokens.json
│ ├── 📄 App.vue
│ └── 📄 main.js
feature 기반의 아키텍처를 설계했고 DDD 설계에서 나눈 도메인과 동일하게 기능을 나눈뒤 각각의 기능에 views, router, components를 관리했고 각각의 도메인에서 사용하는 api들은 한눈에 보기 쉽게 상단에 api 디렉토리 안에서 관리했습니다.
재사용되는 컴포넌트나 레이아웃은 최상단 components 디렉토리에서 관리했고 특정 도메인에 종속된 컴포넌트와 페이지만 각각의 도메인에서 관리하도록 역할을 분리했고 모든 도메인에서 재사용되는 컴포넌트의 경우엔 최대한 재사용할 수 있도록 도메인별 종속성을 없애는데 초점을 맞춰 구현하였습니다.
컴포넌트 및 레이아웃 설계
화면 설계 및 컴포넌트 설계는 위에 개요에서 설명드린 내용과 동일하고 추가적으로 저는 화면을 설계하다보니 공통되는 레이아웃의 종류가 총 3개라는 것을 알게되었습니다.
그 종류는 크게 다음과 같습니다
SidebarMainLayout은 회원쪽 페이지와 모임 및 장소 조회 페이지에서 사용되는 레이아웃입니다.
FullCenterMainLayout은 모든 모달 및 로그인과 같이 수직-수평 가운데 정렬이 필요한 레이아웃입니다.
DefaultMainLayout은 수평으로만 가운데 정렬되는 기본적인 레이아웃입니다.
각각의 레이아웃에 대해서 width, height, paddin 등을 설정했고 공통 레이아웃이기 때문에 특정 도메인에 종속되지 않도록 필요하면 호출하는 쪽에서 class를 전달하여 적용될 수 있도록 구현하였습니다.
그리고 팀원분들이 쉽게 사용할 수 있도록 아래와 같이 컨벤션을 정리하여 공유한 내용은 다음과 같습니다.

피그마 스타일 시스템과 tailwindCSS 연결
저희는 이번 프로젝트에서 tailwindCSS 프레임워크를 사용하여 효율적으로 CSS를 적용할 수 있도록 했습니다.
위의 개요에서 피그마 스타일을 설정하여 화면 설계부터 컬러 팔레트나 폰트 같은 것들을 맞춘바 있다고 했는데요, 피그마에서 설계한 스타일 시스템을 tailwindCSS로 가져오는 것이 디자인 통일성과 화면 설계와 구현을 일치하는데 무엇보다 중요했습니다.
따라서 처음에는 자동화를 위해 Style Dictionary 라이브러리를 사용하여 피그마 디자인 토큰 스튜디오 플러그인에서 추출한 JSON 파일을 TailwindCSS 버전으로 변환하는 자동화 과정을 구현했으나 마음처럼 되지는 못했고 어쩔 수 없이 수동으로 tailwind.config.js 파일을 피그마 스타일 시스템과 동일하게 맞춰 화면 설계와 구현의 일관성을 맞추는데 성공하게 되었습니다.
다만 아직 자동화를 하지 못했기 때문에 화면 설계가 모두 맞치지 못한 상태에서 디자인과 개발이 동시에 일어나야 하는 상황이라면 유지보수가 어렵다는 문제가 아직 남아 있기 때문에 다소 아쉬운 구현이라고 할 수 있습니다.
따라서 다음 최종 프로젝트에선 꼭 피그마 -> tailwind.config.js 변환을 자동화하는 시스템을 구축하는 것을 목표로 하고자 합니다.
전역 인증 상태 관리 및 네비게이션 가드 설정
저는 백엔드 프로젝트때도 회원 및 인증 및 인가 상태 관리 전체를 담당했었기 때문에 프론트엔드 개발에 있어서도 해당 도메인을 주로 맡아 담당했습니다.
우선 기존 Postman에서 api 테스트를 진행했을땐 CORS 정책에 대한 고민을 안했었는데 Vue 호스팅 도메인에서 axios로 api를 연결할 땐 다른 도메인에서 호출을 하게되므로 CROS 문제가 발생하였고 저희는 Spring Gateway에서 모든 인증을 처리했기 때문에 Spring Gateway의 application.yml에서 다음과 같이 CORS 설정을 추가하게 되었습니다.

그리고 기존에는 로그인을 성공하게 되면 응답으로 body에 accessToken과 refreshToken을 담아서 보냈는데 그렇게 되면 accessToken과 refreshToken 모두 localStorage나 인메모리나 세션등에 저장해야 하는데 accessToken은 유효시간이 짧기 때문에 문제가 될 여지가 적지만 refreshToken을 localStorage나 인메모리에 저장하는 것은 스크립트 구문 실행을 통해 탈취될 여지가 있기 때문에 스크립트로 접근할 수 없는 HttpOnly 쿠키에 담아서 보내는 것으로 수정하였고 accessToken은 접근성과 짧은 유효시간을 고려하여 인메모리, 즉 Pinia 전역 상태에서 관리하는 것으로 결정하였습니다.
이와 관련하여 추가한 메서드는 다음과 같습니다.

쿠키 설정에 대한 소스코드들은 유틸 클래스로 관리하도록 구현했습니다.

그리고 Vue에서 login api를 호출하여 전달받는 accessToken뿐만 아니라 페이지내에서 빈번하게 재사용되는 값들은 로그인시에 body에 추가적으로 포함하여 전달하고 이것들을 모두 useAuthStore에서 관리했습니다.

인증과 관련한 모든 코드들 예로들어 login, logout, refresh, clearAuth 메서드는 모두 useAuthStore에서 집중 관리하도록 하였고 필요시에 가져다 사용할 수 있도록 구현했습니다.

그리고 새로고침 시에 accessToken이 초기화가 되면서 인증 상태게 해제되는 문제가 있는데 해당 문제는 main.js에서 초기화와 관련된 코드들을 함수로 묶어서 마운트 시점에 호출될 수 있도록 하였고 매번 초기화 될 때마다 authStore에서 관리하는 refresh 메서드를 호출하여 현재 HttpOnly 쿠키에 저장된 refreshToken으로 서버에 전달하여 refreshToken이 DB에서 관리되고 있는 값이 맞다면 새로운 accessToken과 refreshToken을 다시 전달하여 인증 상태를 유지할 수 있도록 구현하였습니다.

그리고 저희는 총 3개의 권한이 있습니다. user, business, admin인데요. 페이지별로 접근 권한이 각기 달랐기 때문에 도메인으로 접속하지 못하도록 네비게이션 가드를 설정해야 했고 인증이 필요한 페이지의 경우에는 설정한 라우터 메타 정보로 판별했고 권한의 경우엔 useAuthStore에서 관리하는 role로 접근 여부를 판별하여 권한별 가드를 설계했습니다.
그리고 인증이 필요한 페이지의 경우에는 로그인 리다이렉트 하도록 설정했는데 이때 사용자 경험을 향상하기 위해 로그인 성공을 하게 되면 원래 이동하려고 했던 도메인으로 이동 할 수 있도록 query에 리다이렉트 정보를 포함하고 다음 페이지 이동시에 해당 정보를 추출하여 해당 페이지로 이동할 수 있도록 구현하였습니다.

axios 인터셉터를 통한 전역 요청 및 응답 관리
저는 저번 백엔드 프로젝트부터 응답에 대한 통일성을 무엇보다 중요시했고 예외처리도 모두 동일한 ErrorResponse를 사용하도록 구조를 설계했습니다.
그래서 프론트에서도 axios 인터셉터를 설정하여 성공 응답의 경우엔 성공 toast 알림이 뜨도록 설정하고 에러 응답의 경우엔 ErrorCode에 따라 응답 자체가 없으면 네트워크 에러로 판별하였고 만약에 refresh token에 대한 요청중 401 에러가 나면 토큰이나 인증에 문제가 있는 것이기 때문에 바로 로그아웃 처리가 되도록 하였고 그 외의 api 호출에 401 에러가 발생했고 만약에 그것이 2번 이상 동일한 api 호출의 경우라면 로그아웃 처리하도록 하였습니다.
왜냐하면 api 호출에 있어서 401 에러가 발생할 수 있는 가능성은 JWT 토큰의 유효시간이 만료된 것으로만 한정을 했고 그런 경우에는 refresh 메서드를 호출하여 accessToken과 refreshToken을 새로 발급받아서 기존 api 연결을 다시 보내도록 설정했는데 만약에 2번 호출했는데도 401에러가 발생하면 토큰에 문제가 있다는 것이기 때문에 무조건 로그아웃 처리하도록 하였습니다.
그리고 추가적으로 저희는 ErrorCode를 명확하게 분기했기 때문에 403 에러가 나면 권한이 없다는 Toast 알림을 날리도록 했고 기타 500 에러면 서버에 문제가 있다는 알림을 전송하도록 하였습니다.
그리고 요청시에도 Pinia 전역 상태에서 관리하는 JWT 토큰을 Header의 Authorization에 넣어 보낼 수 있도록 요청 시 인터셉터를 설정하기도 했습니다.

프론트엔드 프로젝트를 3주 정도 진행한 것 같은데 3개월은 한 것만큼 치열하게 준비하긴 했지만 욕심이 많았던 탓에 아쉬움이 크게 남는 것 같습니다.
저번 백엔드 프로젝트에서 문서화에 대한 중요성을 인식하고 이번에 컨벤션을 정리하려고 노력했지만 저 역시 Vue를 처음 사용하고 익히는데 급급하다 보니 모든 컨벤션을 정리하는데 큰 어려움을 느꼈고 다음 프로젝트에선 컨벤션을 미리 모두 정하고 진행하는 것이 중요할 것 같습니다.
그러면 여태까지 긴 글 읽어주셔서 감사드리고 다음 프로젝트 회고에서 뵙도록 하겠습니다. 감사합니다!
다음 프로젝트 회고 기대!!