🎁[속보] 인프런 내 깜짝 선물 출현 중🎁

[인프런 워밍업 스터디 클럽 3기 풀스택] 1주차 발자국

[인프런 워밍업 스터디 클럽 3기 풀스택] 1주차 발자국

[풀스택 완성] Supbase로 웹사이트 3개 클론하기 (Next.js14)




학습 내용

섹션 1. 오리엔테이션 ~ 섹션 3. 연습 프로젝트 - TODO LIST 만들기.

이번 주는 라이브러리(or 프레임워크) 간략 소개 및 기본 문법이 주를 이룬다고 할 수 있다.

라이브러리 사용법은 공식문서나 강의노트를 참고하면 좋을 것 같고,

라이브러리(or 프레임워크)를 왜 사용하는 지를 적어두려고 한다.

 

Supabase

image

image

Supabase는 와 같은 장점단점을 가진다고 한다.

 

장점

1. 오픈소스 프로젝트 (자체 서버구축 가능)

  • 완전한 오픈소스로 제공되어 무료로 사용 가능

  • 자체 서버에 직접 설치하고 운영 가능

  • Docker를 통한 쉬운 배포와 구성

  • 커뮤니티 기반의 지속적인 발전과 버그 수정

  • 기업의 보안 정책에 맞춰 커스터마이징 가능

2. PostgreSQL 기반 (관계형 DB 장점을 살릴 수 있다)

  • 강력한 PostgreSQL의 모든 기능 사용 가능

  • 복잡한 관계형 데이터 모델링 지원

  • 트랜잭션 및 ACID 속성 보장

  • 강력한 쿼리 최적화와 인덱싱

  • 풍부한 데이터 타입과 확장 기능

  • 외래 키 제약조건을 통한 데이터 무결성 보장

3. Firebase 대비 저렴

  • 무료 티어의 관대한 제공

  • 사용량 기반의 합리적인 가격 정책

  • 자체 호스팅 시 비용 절감 가능

  • 예측 가능한 비용 구조

  • 과금 체계의 투명성

  • 불필요한 기능에 대한 과금 없음

4. 다양한 연동방식 지원 (+ GraphQL, API, SDK, DB Connection)

  • RESTful API 자동 생성

  • GraphQL 인터페이스 기본 제공

  • 다양한 프로그래밍 언어를 위한 공식 SDK 제공

  • JavaScript/TypeScript

  • Python

  • Dart/Flutter

  • 기타 다양한 언어

  • 실시간 데이터 구독 기능

  • 직접적인 데이터베이스 연결 지원

  • OAuth 및 소셜 로그인 통합

  • 자동 생성된 API 문서

     

단점

. 아직 성숙하지 않은 커뮤니티 기반

  • Firebase 대비 작은 커뮤니티 규모

  • 상대적으로 적은 레퍼런스와 예제 코드

  • 문제 해결을 위한 자료 부족

2. 비교적 적은 기능들, 적은 서비스 연동 지원

  • Firebase 대비 제한된 기능 세트

  • 서드파티 서비스 통합 옵션 부족

  • 일부 고급 기능 미지원

3. 부족한 문서화, 한글 문서 부족

  • 불완전하거나 업데이트가 늦은 문서

  • 한국어 문서의 절대적 부족

  • 일부 기능에 대한 설명 부실

4. Firebase보다 높은 러닝커브

  • PostgreSQL 지식 필요

  • 초기 설정의 복잡성

  • 상대적으로 더 많은 학습 시간 요구


Next.js

Next.js는 내용이 방대하기에 정리라기보다는 공식문서의 Next.js에 대한 소개로 대체하겠다.

Next.js란?

풀스택 웹 애플리케이션을 구축하기 위한 React 프레임워크입니다. React의 기본 기능에 추가적인 최적화와 기능을 제공하며, 개발자가 복잡한 설정 대신 애플리케이션 개발에 집중할 수 있게 해줍니다.

주요 기능

1. 라우팅 시스템

  • 파일 시스템 기반 라우터

  • 레이아웃, 중첩 라우팅 지원

  • 로딩 상태, 에러 처리 기능

2. 렌더링

  • 클라이언트/서버 사이드 렌더링

  • 정적/동적 렌더링

  • Edge와 Node.js 환경에서의 스트리밍

3. 데이터 페칭

  • 서버 컴포넌트에서 async/await 사용

  • 요청 메모이제이션

  • 데이터 캐싱과 재검증

4. 스타일링

  • CSS Modules

  • Tailwind CSS

  • CSS-in-JS 지원

     

5. 최적화

  • 이미지 최적화

  • 폰트 최적화

  • 스크립트 최적화

6. TypeScript

  • 향상된 타입 체크

  • 효율적인 컴파일

  • 커스텀 TypeScript 플러그인

라우터 종류

  1. App Router: 최신 라우터(v13.4.0 이후)

  • 서버 컴포넌트

  • 스트리밍 지원

  • 최신 React 기능 활용

  1. Pages Router: 기존 라우터

  • 서버 사이드 렌더링

  • 기존 프로젝트 지원

  • 안정성 검증됨

     

Tailwindcss

유틸리티 우선(utility-first) 방식의 CSS 프레임워크로, 미리 정의된 클래스들을 조합하여 스타일링하는 방식을 제공한다.

// 유틸리티 우선 접근
<!-- 기존 CSS 방식 -->
<div class="chat-notification">
  <p class="chat-title">새 메시지</p>
</div>

<!-- Tailwind 방식 -->
<div class="flex items-center p-4 bg-white rounded-lg shadow-md">
  <p class="text-lg font-semibold text-gray-700">새 메시지</p>
</div>

장점

  • 빠른 개발: 클래스 이름을 고민할 필요 없음

  • 일관성: 미리 정의된 디자인 시스템 제공

  • 커스터마이징: 설정 파일을 통한 쉬운 확장

  • 번들 크기: 사용한 클래스만 포함되어 최적화됨

  • 반응형 디자인: 내장된 반응형 클래스 제공

단점

  • 긴 클래스명

  • 초기 학습 곡선

     

Recoil

Facebook에서 개발한 React 전용 상태 관리 라이브러리

(23년 이후로 깃허브 저장소가 업데이트되고 있지 않는 이슈가 있어, 다른 라이브러리가 권장된다...)

주요 특징

  • React 전용으로 설계된 상태 관리 도구

  • 간단하고 직관적인 API

  • 비동기 데이터 처리 지원

  • 상태 파생과 캐싱 기능

  • TypeScript 지원

1. Atom - 기본 상태 단위

// atoms/todoAtom.ts
import { atom } from 'recoil';

// 할 일 목록 상태
export const todosState = atom({
  key: 'todosState',
  default: [],
});

// 필터 상태
export const todoFilterState = atom({
  key: 'todoFilterState',
  default: 'all', // 'all' | 'completed' | 'uncompleted'
});

2. Selector - 파생 상태

// selectors/todoSelector.ts
import { selector } from 'recoil';
import { todosState, todoFilterState } from '../atoms/todoAtom';

export const filteredTodosState = selector({
  key: 'filteredTodosState',
  get: ({get}) => {
    const filter = get(todoFilterState);
    const todos = get(todosState);

    switch (filter) {
      case 'completed':
        return todos.filter((todo) => todo.completed);
      case 'uncompleted':
        return todos.filter((todo) => !todo.completed);
      default:
        return todos;
    }
  },
});

export const todoStatsState = selector({
  key: 'todoStatsState',
  get: ({get}) => {
    const todos = get(todosState);
    const total = todos.length;
    const completed = todos.filter((todo) => todo.completed).length;
    const uncompleted = total - completed;

    return {
      total,
      completed,
      uncompleted,
      percentCompleted: total === 0 ? 0 : (completed / total) * 100,
    };
  },
});

3. 컴포넌트에서 사용 예시

// components/TodoList.tsx
import { useRecoilState, useRecoilValue } from 'recoil';
import { todosState, todoFilterState } from '../atoms/todoAtom';
import { filteredTodosState, todoStatsState } from '../selectors/todoSelector';

export function TodoList() {
  // 상태 읽기와 쓰기가 모두 필요한 경우
  const [todos, setTodos] = useRecoilState(todosState);
  const [filter, setFilter] = useRecoilState(todoFilterState);
  
  // 읽기만 필요한 경우
  const filteredTodos = useRecoilValue(filteredTodosState);
  const stats = useRecoilValue(todoStatsState);

  const addTodo = (text: string) => {
    setTodos([
      ...todos,
      {
        id: Date.now(),
        text,
        completed: false,
      },
    ]);
  };

  const toggleTodo = (id: number) => {
    setTodos(
      todos.map((todo) =>
        todo.id === id ? { ...todo, completed: !todo.completed } : todo
      )
    );
  };

  return (
    <div>
      {/* 필터 컨트롤 */}
      <div>
        <select value={filter} onChange={(e) => setFilter(e.target.value)}>
          <option value="all">모두 보기</option>
          <option value="completed">완료된 항목</option>
          <option value="uncompleted">미완료 항목</option>
        </select>
      </div>

      {/* 통계 표시 */}
      <div>
        <p>전체: {stats.total}</p>
        <p>완료: {stats.completed}</p>
        <p>미완료: {stats.uncompleted}</p>
        <p>완료율: {stats.percentCompleted.toFixed(1)}%</p>
      </div>

      {/* 할 일 목록 */}
      <ul>
        {filteredTodos.map((todo) => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            <span>{todo.text}</span>
          </li>
        ))}
      </ul>
    </div>
  );
}

React Query(Tanstack Query)

서버 상태 관리를 위한 강력한 라이브러리로, 데이터 페칭, 캐싱, 동기화, 업데이트를 효율적으로 처리

1. 기본 사용법(Query)

// 기본적인 쿼리 사용
function TodoList() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['todos'],
    queryFn: fetchTodos,
  });

  if (isLoading) return <div>로딩 중...</div>;
  if (error) return <div>에러 발생!</div>;

  return (
    <ul>
      {data.map(todo => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
}

2. 데이터 변이(Mutation)

function AddTodo() {
  const mutation = useMutation({
    mutationFn: (newTodo) => axios.post('/todos', newTodo),
    onSuccess: () => {
      // 캐시 무효화 및 쿼리 다시 가져오기
      queryClient.invalidateQueries({ queryKey: ['todos'] });
      toast.success('할 일이 추가되었습니다!');
    },
  });

  return (
    <button onClick={() => mutation.mutate({ title: '새로운 할 일' })}>
      할 일 추가
    </button>
  );
}

3. 자동 캐싱 및 재검증

// 캐시 시간 및 재시도 설정
const { data } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
  staleTime: 5 * 60 * 1000, // 5분
  cacheTime: 30 * 60 * 1000, // 30분
  retry: 3, // 실패시 3번 재시도
});

4. 의존적 쿼리

function UserTodos({ userId }) {
  // userId가 있을 때만 쿼리 실행
  const { data: todos } = useQuery({
    queryKey: ['todos', userId],
    queryFn: () => fetchUserTodos(userId),
    enabled: !!userId,
  });
}

5. 무한스크롤 / 페이지네이션

function InfiniteTodos() {
  const {
    data,
    fetchNextPage,
    hasNextPage,
    isFetchingNextPage,
  } = useInfiniteQuery({
    queryKey: ['todos'],
    queryFn: ({ pageParam = 0 }) => fetchTodoPage(pageParam),
    getNextPageParam: (lastPage) => lastPage.nextCursor,
  });

  return (
    <div>
      {data.pages.map((page) => (
        page.todos.map(todo => <TodoItem key={todo.id} todo={todo} />)
      ))}
      <button
        onClick={() => fetchNextPage()}
        disabled={!hasNextPage || isFetchingNextPage}
      >
        {isFetchingNextPage
          ? '로딩 중...'
          : hasNextPage
          ? '더 보기'
          : '더 이상 없음'}
      </button>
    </div>
  );
}

장점

  1. 자동 캐싱

    1. 중복 요청 방지

       

    2. 데이터 신선도 관리

    3. 메모리 효율적 관리

  2. 서버 상태 동기화

    1. 백그라운드 업데이트

       

    2. 자동 재시도

    3. 에러 처리

  3. 개발자 경험

    1. DevTools 제공

    2. TypeScript 지원

    3. 직관적인 API

  4. 성능 최적화

     

    1. 자동 가비지 컬렉션

    2. 요청 중복 제거

    3. 스마트 리페칭

단점

  1. 학습 곡선

    1. 복잡한 개념들 (staleTime, cacheTime, invalidation 등)

    2. 최적의 설정을 위한 깊은 이해 필요

    3. 다양한 옵션들로 인한 초기 진입 장벽

  2. 번들 크기

    1. 기본 번들 사이즈가 큰 편 (약 12KB gzipped)

    2. 작은 프로젝트에서는 과도할 수 있음

       

  3. 보일러플레이트

     

    1. 반복적인 쿼리 설정 코드

    2. 전역 설정을 위한 추가 코드 필요

    3. TypeScript 사용 시 타입 정의 부분이 길어질 수 있음

  4. 상태 관리 한계

     

    1. 클라이언트 상태 관리에는 부적합

    2. 서버 상태 전용이라 별도의 상태 관리 도구 필요

    3. 때로는 다른 상태 관리 라이브러리와 함께 사용해야 함

  5. 캐시 관리 복잡성

     

    1. 복잡한 캐시 무효화 로직

    2. 캐시 키 관리의 어려움

    3. 잘못된 설정 시 메모리 누수 가능성



미션 1

TODO LIST 중

  1. TODO 항목 옆 생성 시간 표시

  2. (선택) completed_at 필드를 추가하여 완료한 시간도 함께 저장

image

1번은 수업중 todo 테이블을 만들었을 때 created_at을 받아와서 배치만 하면 끝이고,

2번은 edit table을 해서 completed_at column을 추가하여 배치하면 사실상 끝이었다.

 

깃허브 저장소: https://github.com/kelvin-chihyun/next-inflearn/tree/main/apps/todo

 


회고

사실 1주차는 맛보기와 같다.

Next.js의 기본적인 기능(라우팅, 렌더링, 데이터 페칭 등)에 대해서는 알고 있는 편이라, 복습하는 느낌으로 보았다.

 

이번 풀스택 워밍업 클럽에서 개인적으로 얻고자 하는 것은 강의 내용은 베이스로 두고,

다른 기술을 사용한다거나 포인트를 두는 것이다.

(강사님이 잘 가르쳐주시는 것은 당연하지만, 나 자신이 수동적으로 임하면 제대로 안할 것 같았다.)

 

이번 주에는 아직 한번도 사용해보지 않고 알고만 있던 Turbo Monorepo로 프로젝트를 구성하는 것을 목표로,

TO DO LIST를 만들기보다는 모노레포 구성에서 나오는 여러 경로 인식 에러들을 처리하는 데에 힘을 썼던 것 같다.

배포는 어떻게 해야할지 아직 감도 안잡히지만, 이어지는 프로젝트를 수행하면서 배포도 해보려고한다.

 

구성 방법에 대해 이해가 된 건 아니지만, 어찌됐든 구성 자체는 의도대로 만들어서 뿌듯한 이번 주였다.

(투두리스트의 기능이 다소 없는 것 같아 아쉬운 부분이 있는데, 이 부분은 추후에 디벨롭해보려고 한다.)

댓글을 작성해보세요.


채널톡 아이콘