개인 프로젝트 - blequotes 마무리
포스트
취소

개인 프로젝트 - blequotes 마무리

해당 포스트는 개인 프로젝트인 blequotes 마무리 포스트입니다.

😶 blequotes란

ble는 제가 만든 프로젝트에 식별자처럼 사용하는 접두사입니다. ( 여러가지 조합하다가 GitHub이름인 1-blue의 일부분인 ble를 사용하게 되었습니다… 🙂 )

영화 / 드라마 / 도서의 명대사를 간단하게 작성하는 커뮤니티입니다.

🧑‍💻 사용한 기술

  1. TypeScript
  2. React.js
  3. TailwindCss
  4. Redux-Toolkit
  5. React-Hook-Form
  6. Express
  7. Prisma
  8. AWS-S3
  9. AWS-EC2 ( 사용 예정 )

🔨 사용한 툴

  1. Git / GitHub
  2. Notion
  3. SourceTree
  4. VSCode
  5. Figma ( 처음부터 사용한 것은 아님 )

🕹️ 구현 기능

  1. Movie DB API를 이용한 영화 및 드라마들의 각종 정보 패치 및 검색
  2. Kakao Book API를 이용한 도서 검색
  3. Image Carousel ( react-slick 사용 )
  4. 명대사 등록 기능 ( AWS-S3presignedURL 기능을 이용한 이미지 등록 )
  5. 명대사에 좋아요 및 싫어요 기능
  6. 영화 / 드라마 / 도서 검색 기능 ( Debouncing 사용 )
  7. 무한 스크롤링 ( Intersection-Observer 사용 )
  8. React-ToolKit을 이용한 전역 상태 관리
  9. Prisma를 이용한 DB관리 및 데이터 관리

🍀 제작 환경

  1. OS: Window11
  2. editor: VSCode, Sourcetree ( + Notion, Figma )
  3. terminal: git bash
  4. Database: Mysql
  5. vcs: Git / GitHub
  6. Front: React.js
  7. Back: NodeExpress
  8. 이미지 저장소: AWS S3
  9. 배포: AWS-EC2 예정 ( 배포후 수정 )

🃏 알아둬도 쓸데없는 잡다한 이야기

0️⃣ 메타데이터 처리

react-helmet-async를 이용해서 메타데이터를 처리했습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import { Helmet } from "react-helmet-async";

type Props = {
  title: string;
  description: string;
  image?: string;
};

const HeadInfo = ({ title, description, image = "/logo.png" }: Props) => {
  return (
    <Helmet>
      <title>{title + " ⟪ 1-blue ⟫"}</title>
      <meta name="description" content={description} />

      {/* FIXME: URL 결정되면 수정 */}
      {/* <meta property="og:url" content="" /> */}
      <meta property="og:title" content={title} />
      <meta property="og:description" content={description} />
      <meta property="og:image" content={image} />

      <meta name="twitter:card" content={title + "\n" + description} />
      <meta name="twitter:title" content={title} />
      <meta name="twitter:description" content={description} />
      <meta name="twitter:image" content={image} />
    </Helmet>
  );
};

export default HeadInfo;

1️⃣ 웹 접근성을 위한 노력

  1. 전체적인 레이아웃은 시멘틱 태그들을 이용해서 처리했고, <div>보다는 의미 있는 태그들을 사용하려고 노력
    ( <header>, <main>, <footer>, <aside>, <article>, <section>, <ul>, <li>, <time>, <figure> 등 )
  2. <label>, alt 속성 등을 사용
  3. active, focus, hover 등에 일관성을 유지하기 위한 스타일을 적용
  4. 잘못된 접근은 예외 페이지로 이동시킴
  5. 추천 검색어 방향키로 이동하는 기능

2️⃣ 타입 설계를 위한 노력

타입을 최대한 나누고 새로운 타입이 필요한 경우 기존 타입을 이용해서 새로운 타입을 만들었고 유틸리티 타입을 최대한 이용해서 기존 타입에 의존적이도록 코드를 구현하려고 노력했습니다.

또한 이펙티브 타입스크립트에서 본 것처럼 함수 시그니처 타입을 적극적으로 사용했고, React에서도 제공해주는 Handler 타입을 사용했습니다.

3️⃣ API 설계

API에서 확인할 수 있습니다.

HTTP API의 규칙을 지키려고 노력했고, HTTP Method, URI, HTTP Status Code를 이용해서 데이터를 송/수신했으며, 어떤 데이터 형태로 주고 받을지 상세하게 작성했습니다.
또한 TypeScript를 이용해서 송/수신 타입을 만들어서 적극적으로 사용했습니다.

4️⃣ 반응형 레이아웃

TailwindCss 반응형 디자인을 이용해서 400px, 500px, 640px, 768px, 1024px 등의 사이즈에 맞게 레이아웃을 배치했습니다.
또한 기본 폰트는 16px, 400px이하일 경우 폰트는 14px로 조정했습니다.

5️⃣ 빌드와 실행 ( React + Express )

현재 계획으로는 배포를 하면 하나의 서버에서 프론트, 백엔드, 데이터베이스를 모두 돌릴 계획이기 때문에 빌드한 React의 결과물을 Express의 정적 파일 제공 기능을 통해서 서버에 제공합니다. ( 참고 )

단, React로 만든 페이지는 SPA이기 때문에 라우팅에 대한 특별한 처리가 필요합니다.
( 특정 페이지에서 새로고침 시 404 오류 발생 )

1
2
3
4
5
6
7
8
9
10
11
12
// app.ts ( 진입점 ) ( ... 나머지 생략 )

// deploy
app.use(express.static(path.join(__dirname, "../../frontend/build")));
app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, "../../frontend/build", "index.html"));
});

// SPA routing 새로고침 문제 해결을 위한 코드
app.get("*", (req, res) => {
  res.sendFile(path.resolve(__dirname, "../../frontend/build", "index.html"));
});

6️⃣ 좋아요/싫어요 구현에 대한 비밀

현재 웹사이트는 로그인 기능이 없기 때문에 이용자를 식별할 방법이 없습니다.

좋아요/싫어요를 누르면 기존에 눌렀는지를 판단할 근거가 필요한데 로그인 기능이 없기 때문에 판단할 방법이 없어서 LocalStorage를 이용해서 좋아요/싫어요에 대한 데이터를 기억하도록 만들었습니다.

물론 누군가 LocalStroage를 지워버리면 같은 게시글에 중복된 좋아요/싫어요가 가능한 문제가 있지만, 실제 제공할 서비스가 아니기 때문에 큰 문제는 없을 거라고 판단했습니다.

🥲 부족한 것들

0️⃣ 메모이제이션 함수들의 사용

useCallback(), useMemo(), memo()의 사용에 대한 명확한 기준을 정하기 힘들어서 일단 마구잡이로 사용했습니다.

useCallback(), useMemo()는 모든 함수와 변수에 필수적으로 사용했고, memo()props가 자주 바뀌지 않을 컴포넌트라고 판단되면 사용했습니다.
( <Header>, <Footer>, <GridPosts>, <Icon>에 사용함 )

1️⃣ lazy loading

처음에는 지연로딩을 사용하려고 했는데 딱히 쓸만한 곳도 없는 것 같고, 쓴다고 크게 성능에 영향을 미칠거라고 생각이 들지 않아서 사용하지 않았습니다.
( 처음부터 고려하지 않고 마지막에 생각하다 보니 어디에 쓸지 애매한 부분도 있었습니다. )

📸 실행 영상

0️⃣ 반응형 레이아웃 1

반응형 레이아웃 1 반응형 레이아웃 1

1️⃣ 반응형 레이아웃 2

반응형 레이아웃 2 반응형 레이아웃 2

2️⃣ 무한 스크롤링

무한 스크롤링 무한 스크롤링

3️⃣ 스켈레톤 UI

스켈레톤 UI 스켈레톤 UI

4️⃣ 검색

검색 검색

5️⃣ 게시글 생성

게시글 생성 게시글 생성

🧐 다른 포스트들

  1. Redux
  2. React 스크롤 방향 찾기
  3. React-Router-Domreplace
  4. AWS-S3 - presignedURL 사용 방법
  5. Node.js + TypeScript 세팅 방법
  6. Intersection-Observer-API와 무한 스크롤링
  7. prisma 사용법 정리
  8. React-ToolKit + TypeScript + React 사용 방법

📮 레퍼런스

  1. React 배포 방법
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

자바스크립트 완벽 가이드 9장 정리 ( Class )

싱글톤 ( Singleton )