🎁 모든 강의 30% + 무료 강의 선물🎁

인프런 워밍업 클럽 3기 풀스택 - 4주차 발자국

인프런 워밍업 클럽 3기 풀스택 - 4주차 발자국

4주차 학습 내용


Part 1. Git Repository 생성 및 초기 설정 진행

  • 이번 프로젝트는 기존 강의에서 학습했던 방식대로 GitHub에 레포지토리를 생성하고, 초기 설정을 진행하며 시작했다.

  • 폴더 구조와 기본적인 세팅은 이전과 동일하게 구성하여 빠르게 시작할 수 있었다.

    새롭게 만든 message 테이블에는 다음과 같은 컬럼들을 추가했다.

    • id: 기본 키 (primary key)

    • message: 메시지 내용

    • sender: 보낸 사람의 UUID

    • receiver: 받는 사람의 UUID

    • is_deleted: 삭제 여부 (boolean)

    • created_at: 생성 시간 (timestamp)

 

Part 2. 회원가입, 로그인 화면 제작

  • 이전에 하던대로 틀 잡고, components로 구분지으면서 제작했다.

  • 새롭게 배운 부분으로는 배경색을 그라데이션으로 준 부분이였다.

     

  •  테일윈드 css로 편하게 gradient값을 줬다.


    사용법
    bg-gradient-to-r: 그라디언트를 오른쪽 방향으로 흐르게 함 / ex) to-r = to right

     

     

    from-{color}: 그라디언트의 시작 색상 지정 / ex) from-green-400

     

    to-{color}: 그라디언트의 끝 색상 지정 / ex) to-blue-500

    via-{color}: 그라디언트의 중간 색상 지정 / ex) via-pink-500

     

 

image

Part 3. Supabase Auth 소개 및 인증방식 기획

  • Supabase Auth는 이메일 기반 인증부터 소셜 로그인까지 다양한 인증 방식을 제공하며, 회원가입, 로그인, 세션 유지 등을 쉽게 처리할 수 있는 백엔드 인증 서비스다.

1. Confirmation URL 방식

  • 사용자 이메일로 인증 링크를 전송하고, 사용자가 해당 링크를 클릭함으로써 인증이 완료되는 방식이다.

  • 사용자가 회원가입(또는 로그인)을 하면, 이메일로 인증 링크가 전송되고

  • 사용자가 해당 링크를 클릭하면 Supabase가 사용자의 인증을 완료함 

 

2. 6-Digit OTP 방식

  • 사용자 이메일로 6자리 숫자 코드(OTP)를 전송하고 사용자가 해당 코드를 입력해서 인증하는 방식 

     

  • 사용자가 이메일을 입력하면, Supabase는 해당 이메일로 6자리 OTP 코드를 전송하고

  • 사용자는 입력창에 이 코드를 입력하고 인증을 완료하게 된다. 

 

Part 6. 채팅 화면 구현

1. 유저 아바타 랜덤 이미지 사용

  • https://randomuser.me/photos API를 활용해 유저 프로필 사진을 랜덤으로 가져옴

  • 별도 이미지 업로드 없이 테스트용 아바타를 빠르게 구현할 수 있음

     

     

    const randomImage = https://randomuser.me/api/portraits/men/${index}.jpg;

     

2. javascript-time-ago 라이브러리

  • 채팅 메시지 시간 표시를 "1분 전", "3시간 전"처럼 사람이 보기 쉬운 형태로 변환

  • 국제화(i18n)도 지원함 (예: 한글/영어/중국어 등)

import TimeAgo from "javascript-time-ago";
import ko from "javascript-time-ago/locale/ko.json";

TimeAgo.addDefaultLocale(ko);
const timeAgo = new TimeAgo("ko");

timeAgo.format(new Date()) // → "방금 전"

 

Part 7. Supabase Realtime 소개 & 채팅목록 구현

 Supabase Realtime은 PostgreSQL 데이터베이스의 변경 사항을 실시간으로 감지해서 클라이언트에 push해주는 기능.

기본적으로 WebSocket을 기반으로 작동하고, 내부적으로는 PostgreSQL의 logical replication 기능을 활용한다.

 

🔧 작동 방식

  1. 클라이언트가 WebSocket으로 채널 생성

    • .channel() 메서드를 이용해서 원하는 테이블과 이벤트 종류를 구독함

    • 예: message 테이블의 INSERT 이벤트

  2. Supabase 서버에서 PostgreSQL의 변경 스트림 감지

    • PostgreSQL에서 발생하는 INSERT, UPDATE, DELETE 이벤트를 감지

    • 이를 wal2json 등의 logical decoding plugin을 통해 JSON으로 변환

  3. Supabase Realtime 서버가 이를 브로드캐스트

    • WebSocket 연결된 클라이언트에게 변경된 데이터(payload)를 push로 전달함

  4. 프론트에서 실시간 UI 반영

    • 받은 payload로 상태를 업데이트하거나 refetch()를 통해 데이터를 다시 불러와서 렌더링

Part 9. 배포하기

 

vercel 배포

image

Add New 파일을 열고 프로젝트를 선택해준다.

 

image

나의 깃허브와 연동시켜주고 배포를 원하는 프로젝트를 선택해준다

 

image

Deploy를 누르면 배포가 되게 되는데 그전에 프로젝트의 이름을 정하고,

프로젝트에서 npm run build를 해서 배포시에 발생할 에러가 있는지 미리 체크한다.

그리고 환경변수에는 프로젝트에 있는 .env 파일에 있는 코드를 복사해서 넣어줘야한다.

 

image

사진과 같은 에러가 발생했고, 현재 에러를 해결하는 방법은 2가지가 있다.

하나는 직접 에러의 원인을 찾아 제거하는 방식 = <Spinner onPointerEnterCapture={undefined} onPointerLeaveCapture={undefined} />)}

둘째는 any로 지정하는것 처럼 무시하고 지나가라는식의 방법으로 해결하는 방법이다.

루트에 index.d.ts를 만들고, declare module "@material-tailwind/react";를 해줌으로써 material-tailwind로 발생하는 에러는 넘겨주는 것.

 

image

배포가 끝났고 이제 휴대폰에서도 정상적으로 동작한다!

 


 

미션: 그동안 만든 4개 프로젝트 배포하기

선택 미션에는 채팅 메세지 삭제 기능 이나 채팅 읽음, 안 읽음 표시 등 추가 작업인데 우선 후순위로 미루고 배포에 집중함.

 

 

미션 이외로

미션 이외로 나는 휴대폰으로 채팅하는 모습을 확인하고 싶어서 배포를 진행하고 휴대폰을 확인했다.

하지만 이메일 인증을 하고 이동하는 코드는 아래처럼 되어 있어서 연결이 깨지게 되어 있다.

  const signupMutation = useMutation({
    mutationFn: async () => {
      const { data, error } = await supabase.auth.signUp({
        email,
        password,
        options: {
          emailRedirectTo: "http://localhost:3000/signup/confirm",
        },
      });

      if (data) setConfirmationRequired(true);
      if (error) alert(error.message);
    },
  });

그렇다고 여기서 저부분을 배포 주소로 바꾸자니, 나중에 버그나 새로운 기능을 넣기 위해 작업할때는 또 번거롭게 바꿔줘야 하기때문에 이 문제를 어떻게 해결할지에 대한 고민을 했다.

  const redirectUrl =
    process.env.NEXT_PUBLIC_REDIRECT_URL ||
    (process.env.NODE_ENV === "development"
      ? "http://localhost:3000"
      : "https://supabase-instagram-clone.vercel.app");

  const signupMutation = useMutation({
    mutationFn: async () => {
      const { data, error } = await supabase.auth.signUp({
        email,
        password,
        options: {
          emailRedirectTo: `${redirectUrl}/signup/confirm`, // ✅ 변수 사용
        },
      });

      if (data) setConfirmationRequired(true);
      if (error) alert(error.message);
    },
  });

우선 리다이렉트를 구분하기 위해 로컬호스트에서는 로컬호스트를 보내고 수퍼베이스에서는 수퍼베이스만 보내도록 수정함.

image

버셀에서 환경변수를 추가해주고

image

수퍼베이스에서도 배포한 링크를 연결시켜주니 해결했다.

말은 쉬운데 사실 해결하려고 몇시간을 싸매고 한 결과다..ㅠ

image

성공한 사진!!🎉🎉

UI적으로는 다듬지 못해서 엉망인 모습을 보이고, 남들이 보기엔 별거 아닐지라도 나한테는 휴대폰과 노트북으로 서로 소통을 하는게 이렇게 돌아가는구나 라는것을 느끼고 되게 신기했다.


---

📝 마지막 회고

4주차는 지금까지 중 가장 힘든 한 주였던 것 같다.
평소엔 기능을 하나씩 구현해왔는데 이번 주차는 로그인과 채팅 기능을 함께 다루다 보니 정신없고 벅찼다.

점점 기능 난이도가 깊어지고 있다는 느낌도 들어서, 생각보다 어렵게 다가왔다.
특히 이번 주에는 코드를 따라 치긴 했지만 이해가 부족했던 순간들도 많았고
주말에 큰 약속이 있어서 과제를 빨리 끝내야 한다는 부담감 때문에 급하게 하다 보니 놓친 부분도 많았던 것 같다.

그렇게 어느새 4주차.. 이 스터디의 마지막 주차가 되어버렸다.
돌이켜보면 한 달 전의 나와 비교해 분명 많이 성장한 시간이었다.

 

📌 이번 한 달간, 내가 얻은 것들

  • 처음엔 next.jstailwind를 조금만 다룰 줄 알았고 전역 상태 관리도 zustand 정도만 써봤었다.

  • 하지만 이번 강의를 통해
    next.js의 프로젝트 구조 잡는 방식
    tailwind를 더 효율적으로 사용하는 법
    → 그리고 새롭게 배운 recoil, supabase, server actions, vercel 배포까지!
    정말 다양한 기술들을 직접 써보면서 익힐 수 있었다.

 

🙃 좋았던 순간, 힘들었던 순간

강사님 코드랑 똑같이 썼는데도 에러가 나서 괜히 억울했던 순간,
반대로 내가 직접 구현한 기능이 잘 작동해서 뿌듯했던 순간도 있었다.

물론 한 번 배웠다고 해서 금방 능숙하게 다루진 못하지만
이 강의를 통해 전반적인 흐름을 훑을 수 있었고
이제는 Supabase를 활용해 어떤 기능을 만들어볼까? 상상해보는 재미도 생겼다.

 

🎉 마지막으로

다음엔 이번 주차에서 만들었던 인스타그램 클론을 좀 더 확장해서
게시물 기능까지 구현해보고 싶은 욕심도 있다.

좋은 아이디어가 생긴다면? 더 멋있는 기능을 섞어서 구현해보고 싶다...
어쨌든, 이번 스터디는 정말 기억에 남는 한 달이었다.

한 달 동안 함께 완주해온 스터디 분들 정말 수고 많았고,
좋은 강의 만들어주신 강사님께도 진심으로 감사합니다! 🎉

댓글을 작성해보세요.


채널톡 아이콘