홈페이지를 리뉴얼하게 된 이유
현재 속한 동아리의 홈페이지를 리뉴얼하게 되었다. 사실 2023.09 ~ 2023.11까지 개발 중이었다가 한 번 갈아엎은 것이다. 바닐라 JS 기반 React를 사용하여 AWS S3 배포 자동화까지 구현한 홈페이지를 갈아엎고 TypeScript로 마이그레이션한 이유는 다음과 같다.
- 코드 가독성: 당장의 구현에만 급급해서 주석도 없는 스파게티 코드였다. 내가 학교를 졸업해도 후배들이 계속해서 유지보수/개발을 할 수 있어야했기에 가독성을 높이고 데이터 타입 체크가 가능한 TypeScript가 필요했다.
- 초기 디자인: 기존의 프로젝트는 초기 디자인도 없이 바로 개발에 들어갔었다. 그래서 구현 시 UI 디자인을 어떻게 할 지 고민하는 시간이 길어졌다. 프로젝트 기획 단계에서 초기 디자인을 잡았으면 구현 시에는 신경 쓸 필요가 없었을 것이다.
- 중구난방의 프로젝트 구조: src 하위의 폴더 구조가 엉망이었다. 페이지에 들어갈 컴포넌트들을 src/components 폴더에 몰아넣었는데, 페이지 수가 많아질수록 필요한 컴포넌트를 찾는 시간이 길어졌다. 그래서 프로젝트 구조를 알기 쉽게 개편할 필요를 느꼈다.
- 새로운 기술 욕구: 기존의 프로젝트는 단순히 React + ContextAPI만을 사용했었다. 그러나 새롭게 배운 기술을 써먹고 싶어졌다. 전역 상태관리 라이브러리는 써본 적도 없고, 테스팅이 뭔지도 몰랐던 터라 이번 프로젝트를 하면서 적극 도입하고 싶었다.
위와 같은 이유로 기존의 프로젝트를 갈아엎고 리뉴얼하게 된 것이다. 기술을 선택한 이유가 "그냥 써보고 싶어서"라니 이상하겠지만, 써봐야 좋은지 아닌지를 알 수 있는 법이 아닌가. 프론트 팀 뿐만 아니라 백엔드 팀도 Flask에서 Django로 마이그레이션을 하기로 했다.
디자인
매주마다 비대면 회의를 진행하며 설계 및 디자인, 개발 방향, 데이터 구조에 대해 회의했고 초기 디자인을 완성했다. 그 후 기말고사 기간이 애매하게 겹쳐져서 잠시 프로젝트를 멈추고 겨울방학에 이어가기로 했다.
기술 선택
- Vite: 1년째 업데이트가 없는 CRA는 버리기로 했고 매우 빠른 속도를 자랑하는 Vite를 선택했다.
- React.js: 현재 나와 프론트 팀원이 사용할 줄 아는 유일한 프론트엔드 개발 도구이다.
- TypeScript: 미래를 위해 코드 가독성과 정적 타이핑을 선택했다.
- tailwindcss: 이전에 사용했던 css 툴이라서 선택했다.
- zustand: 겨울방학이라는 짧은 기간 내에 개발해야 했기에 러닝 커브가 낮은 전역 상태관리 툴을 선택해야 했다.
- Storybook: 컴포넌트의 개별 테스트를 위해 선택했다.
큰 틀 구현
겨울방학 2달 동안 홈페이지 리뉴얼 프로젝트를 마무리하기로 했는데, 문제가 생겼다. 내가 "겨울방학 현장실습 인턴"에 합격해버려서 시간을 내기 빠듯해진 것이다. 물론 나는 퇴근하고 몸을 갈아서라도 참여할 생각이지만, 어쩔 수 없는 시간적인 제한이 있을 것은 분명하다. 그래서 팀원들한테 최대한 덜 피해가 가도록.. 인턴을 시작하기 전에 큰 틀이라도 미리 구현하고자 한다.
서버가 구현이 안 되었기 때문에 데이터 통신 기능을 구현하기 보다는 디자인 틀을 구현했다. 모든 페이지에서 공통적으로 사용하는 헤더 네비게이션 바와 바텀 바를 구현하고, 단순 디자인만 구현하는 Main 페이지와 Apply 페이지를 구현했다.
코드
main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import { RouterProvider } from 'react-router-dom';
import { routers } from './router';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<RouterProvider router={routers} />
</React.StrictMode>
);
App.tsx
import { useEffect } from 'react';
import { Outlet, useLocation } from 'react-router-dom';
import GeneralLayout from './pages/GeneralLayout';
import Header from './components/common/Header';
import Bottom from './components/common/Bottom';
function App() {
const location = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [location]);
return (
<GeneralLayout>
<Header />
<Outlet />
<Bottom />
</GeneralLayout>
);
}
export default App;
- GeneralLayout을 상단에 두고 각각의 페이지 컴포넌트들을 Outlet에 나타나도록 했다.
- Header와 Bottom을 모든 페이지에 필요했다.
Header.tsx
import { Link, useLocation } from 'react-router-dom';
import { motion, useScroll } from 'framer-motion';
import Button from '../button/Button';
import { routerData } from '../../router';
import { colors } from './Color';
import { isNotAuthPage, isDarkPage } from '../../utils/location';
function Header() {
const location = useLocation();
const { scrollYProgress } = useScroll();
const headerData = routerData.filter((item) => isNotAuthPage(item.path));
return (
<>
<header className="fixed z-10 w-full h-16 bg-white">
<div className="flex items-center w-full h-full p-7">
{/* TODO: 로고 이미지로 대체해야함. */}
<h3 className="flex justify-start flex-1" id="aid-logo">
AI Developers
</h3>
<div className="flex items-center grow justify-evenly">
{headerData.map((item) => (
<div className="" key={item.label}>
<Link to={item.path}>
<Button label={item.label} />
</Link>
</div>
))}
</div>
<div className="flex justify-end flex-1 gap-4 w-1/8">
<button>Login</button> | <button>Register</button>
</div>
</div>
<motion.div
className="absolute bottom-0 w-full h-1"
style={{
scaleX: scrollYProgress,
transformOrigin: 'left',
backgroundColor: isDarkPage(location.pathname)
? colors.primary
: 'black',
}}
/>
</header>
</>
);
}
export default Header;
- 현재 페이지에 따라 다크 모드나 스크롤바의 색상을 변경해야 했다.
- 이 때, 최대한 컴포넌트 간의 의존도를 낮추기 위해 props 사용을 지양하고 훅을 사용했다.
Bottom.tsx
import { useState, useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function Bottom() {
const [darkMode, setDarkMode] = useState(true);
const location = useLocation();
useEffect(() => {
if (location.pathname === '/apply' || location.pathname === '/login') {
setDarkMode(false);
} else {
setDarkMode(true);
}
}, [location.pathname]);
return (
<div
className={`flex flex-col w-full h-[80vh] px-28 py-16 ${
darkMode
? 'bg-darkdarkgray text-tsecondary'
: 'bg-transparent text-secondary'
}`}
>
<div className="text-xl font-bold">AID (AI Developers)</div>
<br />
<div className="text-md">부산대학교 정보컴퓨터공학부</div>
<div className="text-md">회장 김철수 (asdf1234@gmail.com)</div>
<div className="text-md">부회장 홍길동 (qwer0987@gmail.com)</div>
<div className="flex w-full h-full">
<div className="flex items-center justify-center flex-1 m-12 bg-gray-500">
회장님 프사
</div>
<div className="flex items-center justify-center flex-1 m-12 bg-gray-500">
회장님 개요
</div>
</div>
</div>
);
}
export default Bottom;
결론
디자인을 완성하고 기본적인 뼈대라고 할 수 있는 구조를 잡았으니 문제는 없을 것 같다. 유저 로그인 상태 관리나 스터디 게시판 등 제일 어려운 건 아직 남았지만.. 방학동안 최대한 짬을 내서 퇴근하고 열심히 해봐야지. 그래도 후배들을 위해 코드 가독성도 챙기고 타입도 쓰면서 설계에 대해 계속해서 고민하다보니 배우는 것이 있는 것 같다.
'프로젝트 > 인공지능 동아리, AID' 카테고리의 다른 글
[AID] Zero-shot 기반의 채용공고 필터링 디스코드 훅 (0) | 2024.03.11 |
---|---|
AID 모각코 다녀옴 (0) | 2024.01.23 |
[팀프로젝트] AID 홈페이지 리뉴얼 - (3) API 모킹 (3) | 2024.01.22 |
[팀프로젝트] AID 홈페이지 리뉴얼 - (2) 코드 가독성을 높이기 위한 전략 (1) | 2023.12.28 |