Alleys

전시회 커뮤니티 프로젝트

2024.06 ~

/

3m to read

웹사이트: https://www.alleys.app

요약

목표

  • 전시회 문화에 도움이 될 수 있는 플랫폼 만들기
  • 풀스택 서비스를 개발하며 웹 시스템 디자인 학습하기
  • 상용화 가능한 수준으로 구현하기 (실제 상용화 계획은 없음)

전략

  • MVP - 작은 규모로 시작해서 점진적으로 기능 추가하기
  • 1인 개발 - 기술을 적극적으로 사용하여 비용과 효율 최적화하기

성과

  • MVP 달성 - 24개의 API Endpoint, 12개 페이지
  • 저비용 고효율 달성 - 도메인 제외, 현재 월 $24

시스템 디자인

system design

프론트엔드 기술 스택

Language Typescript
Framework React , NextJS
Utility Zustand , react-hook-form , react-query , minisearch
Style TailwindCSS , react-aria-components , JollyUI
Deploy Cloudflare Pages

구현 1. 인증

signup

세션 사용

  • 인증을 위한 별도의 세션과 쿠키를 사용하여 만료시간을 관리했습니다.
  • 세션의 장점을 활용하여 이메일 인증과 동시에 회원가입을 하도록 구현했습니다.

철저한 보안

  • 이메일을 소유하지 않은 사람이 이메일의 회원가입 여부를 알 수 없도록 설계했습니다.
  • 인증코드의 키 스페이스를 고려하여 인증의 제한시간을 설정했습니다.
  • 잘 모르는 보안 정책은 OWASP의 가이드라인을 참고했습니다.

Future-Proof

  • 쿠키, 세션 그리고 API에 버전을 명시하였습니다.
  • 생성된 세션을 DB에서 추적하고 있습니다. 향후 원격 로그아웃을 구현할 수 있습니다.

확인하기

구현 2. 전시회 목록

요구사항

  • 검색 옵션이 URL에 반영되어 링크를 공유할 수 있습니다.
  • 클라이언트의 네트워크 사용과 연산을 최소화합니다.

Data Fetching: Server vs Client

재사용 가능한 정보를 클라이언트에서 요청하도록 하면 불필요한 네트워크 사용을 줄일 수 있습니다.

Server side (재사용 불가능)Client side (재사용 가능)
전시회 페이지 데이터최대 페이지 수, 전시 장소 ID와 이름 리스트

Flow Diagram

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

  1. 클라이언트에서 전시 장소 리스트를 가공하여 minisearch 인덱스를 만듭니다.
  2. 인덱스를 Zustand에 저장하여 재사용합니다.

확인하기

구현 3. 스타일, 컴포넌트

Screen shot of exhibition detail page

TailwindCSS

반응형 UI를 빠르게 개발하기 위해 사용했습니다.

React Aria Components + JollyUI

원래 RadixUI를 사용하고 있었으나 돌발적인 버그와 이후 도입한 TailwindCSS와의 호환성 문제가 있어 대안으로 찾게 되었습니다. 비교적 알려지지않은 컴포넌트 라이브러리를 선택한 이유는 다음과 같습니다.

  1. 전시회라는 도메인은 접근성이 최우선이기 때문입니다. React Aria Components는 접근성에 대한 높은 기준을 유지하고 있습니다. 실제로 Lighthouse를 확인해보면 접근성 평가에서 항상 100점에 가까운 점수를 보입니다.
  2. Adobe라는 단일 팀에서 React만을 타깃으로 개발되었기 때문입니다. 지금까지 버그가 발생하지 않았습니다.
  3. JollyUI라는 shadcn과 비슷한 라이브러리가 있었기 때문입니다. 스타일과 테마를 바로 쓸 수 있어 개발 속도가 뒤떨어지지 않았습니다.

기본 아바타

유저 업로드 이미지를 지원하기에는 프로젝트 단계가 맞지 않았습니다. 대신 유저마다 고유한 색으로 기본 아바타를 만들어주었습니다.

유저의 이름은 고유하기 때문에 이것으로 SHA-1 해시를 생성하여 컬러로 대치하면 고유한 컬러를 얻을 수 있습니다. 유저에게 알록달록한 재미를 주고, 유저에게 소소한 정체성을 줄 수 있습니다.