웹사이트: https://www.alleys.app
요약
목표
- 전시회 문화에 도움이 될 수 있는 플랫폼 만들기
- 풀스택 서비스를 개발하며 웹 시스템 디자인 학습하기
- 상용화 가능한 수준으로 구현하기 (실제 상용화 계획은 없음)
전략
- MVP - 작은 규모로 시작해서 점진적으로 기능 추가하기
- 1인 개발 - 기술을 적극적으로 사용하여 비용과 효율 최적화하기
성과
- MVP 달성 - 24개의 API Endpoint, 12개 페이지
- 저비용 고효율 달성 - 도메인 제외, 현재 월 $24
시스템 디자인
프론트엔드 기술 스택
Language | Typescript |
Framework | React , NextJS |
Utility | Zustand , react-hook-form , react-query , minisearch |
Style | TailwindCSS , react-aria-components , JollyUI |
Deploy | Cloudflare Pages |
구현 1. 인증
세션 사용
- 인증을 위한 별도의 세션과 쿠키를 사용하여 만료시간을 관리했습니다.
- 세션의 장점을 활용하여 이메일 인증과 동시에 회원가입을 하도록 구현했습니다.
철저한 보안
- 이메일을 소유하지 않은 사람이 이메일의 회원가입 여부를 알 수 없도록 설계했습니다.
- 인증코드의 키 스페이스를 고려하여 인증의 제한시간을 설정했습니다.
- 잘 모르는 보안 정책은 OWASP의 가이드라인을 참고했습니다.
Future-Proof
- 쿠키, 세션 그리고 API에 버전을 명시하였습니다.
- 생성된 세션을 DB에서 추적하고 있습니다. 향후 원격 로그아웃을 구현할 수 있습니다.
구현 2. 전시회 목록
요구사항
- 검색 옵션이 URL에 반영되어 링크를 공유할 수 있습니다.
- 클라이언트의 네트워크 사용과 연산을 최소화합니다.
Data Fetching: Server vs Client
재사용 가능한 정보를 클라이언트에서 요청하도록 하면 불필요한 네트워크 사용을 줄일 수 있습니다.
Server side (재사용 불가능) | Client side (재사용 가능) |
---|---|
전시회 페이지 데이터 | 최대 페이지 수, 전시 장소 ID와 이름 리스트 |
Flow Diagram
서버 구현
// page.tsx
export default async function Page({ params, searchParams }: Props) {
const { env } = getRequestContext()
const localQuery = parseLocalQuery(await searchParams)
if (!localQuery) { // Invalid query
redirect(paths.exhibitions.index + "?preset=current")
}
const [eq, tq] = buildRequestQuery(localQuery)
const exhibitions = fetchExhibitions(env, eq)
return (
<div>
<h1>전시</h1>
<FormProvider defaultValues={getDefaultValues(localQuery)} >
<Options />
<Suspense fallback={(
<DisplayFrame>
{Array.from({ length: 4 }).map((_, i) => (
<DisplayEntry key={i.toString()} />
))}
</DisplayFrame>
)}>
<Display exhibitions={exhibitions} />
</Suspense>
<Pagination totalRequestQuery={tq} currentPage={localQuery.page ?? 1} />
</FormProvider>
</div>
)
}
클라이언트 구현
react-hook-form
Input 상태를 관리하여 렌더링을 효율적으로 실행합니다.
Zustand + minisearch
- 클라이언트에서 전시 장소 리스트를 가공하여 minisearch 인덱스를 만듭니다.
- 인덱스를 Zustand에 저장하여 재사용합니다.
구현 3. 스타일, 컴포넌트
TailwindCSS
반응형 UI를 빠르게 개발하기 위해 사용했습니다.
React Aria Components + JollyUI
원래 RadixUI를 사용하고 있었으나 돌발적인 버그와 이후 도입한 TailwindCSS와의 호환성 문제가 있어 대안으로 찾게 되었습니다. 비교적 알려지지않은 컴포넌트 라이브러리를 선택한 이유는 다음과 같습니다.
- 전시회라는 도메인은 접근성이 최우선이기 때문입니다. React Aria Components는 접근성에 대한 높은 기준을 유지하고 있습니다. 실제로 Lighthouse를 확인해보면 접근성 평가에서 항상 100점에 가까운 점수를 보입니다.
- Adobe라는 단일 팀에서 React만을 타깃으로 개발되었기 때문입니다. 지금까지 버그가 발생하지 않았습니다.
- JollyUI라는 shadcn과 비슷한 라이브러리가 있었기 때문입니다. 스타일과 테마를 바로 쓸 수 있어 개발 속도가 뒤떨어지지 않았습니다.
기본 아바타
유저 업로드 이미지를 지원하기에는 프로젝트 단계가 맞지 않았습니다. 대신 유저마다 고유한 색으로 기본 아바타를 만들어주었습니다.
유저의 이름은 고유하기 때문에 이것으로 SHA-1 해시를 생성하여 컬러로 대치하면 고유한 컬러를 얻을 수 있습니다. 유저에게 알록달록한 재미를 주고, 유저에게 소소한 정체성을 줄 수 있습니다.