블로그
전체 4#카테고리
- 프론트엔드
#태그
- 워밍업클럽3기
- 풀스택
- Next.js
- Supabase
2025. 03. 30.
0
[인프런 워밍업 클럽 3기] 풀스택 스터디 4주차 미션 회고 발자국
학습 내용 요약인프런 워밍업 클럽 3기 풀스택 스터디 4주차에는 Supabase의 Real Time Database와 Authentication, RLS(Row Level Security) 기능을 활용하여 실시간 채팅 기능을 지원하는 인스타그램 클론 프로젝트를 진행해 보았습니다. 인프런에서 발자국을 작성할 때 강의를 보지 않고도 강의 내용을 파악할 수 있을 만큼 자세한 내용을 작성하는 건 지양해 달라고 가이드한 만큼 강의에 대한 필기는 최소화 하고 회고 위주로 적어보겠습니다. Supabase Realtime Database 주요 기능데이터 실시간 업데이트데이터베이스의 INSERT, UPDATE, DELETE 이벤트를 감지특정 테이블, 컬럼 또는 조건에 따라 변경 사항을 필터링 가능PostgreSQL 기반 트리거 (Trigger) 활용기존 Postgres 데이터베이스와 완벽하게 통합트리거(Trigger)를 사용해 복잡한 로직을 추가 가능채널 기반 구독 (Channels & Broadcast)특정 테이블 또는 쿼리에 대해 채널을 구독(Subscribe) 하여 변경 사항 수신클라이언트 간 브로드캐스트(Broadcast) 메시지 전송 가능Row Level Security (RLS) 지원실시간 데이터도 Postgres RLS 규칙을 준수특정 사용자만 특정 데이터에 대한 실시간 업데이트를 수신 가능저지연(Low Latency) 데이터 업데이트PostgreSQL의 LISTEN/NOTIFY를 사용하여 빠른 데이터 전송웹소켓(WebSocket) 기반으로 구현되어 빠른 응답 가능 Supabase Authentication 주요 기능다양한 로그인 방식 지원이메일/비밀번호 로그인 (기본 제공)소셜 로그인 (OAuth) → Google, GitHub, Apple, Discord 등Magic Link (비밀번호 없이 이메일 링크 클릭으로 로그인)OTP (SMS 기반 인증)SAML, OpenID Connect 지원 (엔터프라이즈 인증)Row Level Security (RLS) 통합Postgres의 Row Level Security (RLS) 를 활용하여 사용자별 데이터 접근 제한 가능auth.uid() 함수를 사용하여 현재 로그인한 사용자 필터링 가능JWT 기반 인증 및 커스텀 클레임로그인 시 JWT(JSON Web Token) 가 발급됨필요에 따라 사용자 정의 클레임(Custom Claims) 추가 가능비밀번호 재설정 및 사용자 관리이메일을 통한 비밀번호 재설정 기능 제공사용자 프로필 정보 (auth.users 테이블) 관리 가능기기 기반 인증 및 다중 세션 관리기기별 세션을 유지하고 관리할 수 있음다중 기기 로그인 지원 Supabase RLS 주요 기능사용자별 데이터 접근 제어로그인한 사용자만 자신의 데이터에 접근하도록 설정 가능auth.uid()를 사용해 현재 로그인한 사용자의 UID를 자동 감지PostgreSQL 정책(Policy) 기반 권한 관리CREATE POLICY 를 사용하여 테이블 단위의 접근 정책 설정 가능특정 역할(Role) 또는 조건을 만족하는 사용자만 데이터 접근 허용JWT 기반 인증과 연동Supabase의 Authentication(JWT) 와 함께 사용 가능JWT의 사용자 ID(UID), 이메일, 역할(Role) 정보를 활용하여 정책 적용유연한 권한 설정읽기(Read) 전용 정책 설정 가능소유자(Owner) 기반 정책을 설정하여 자신의 데이터만 수정 가능특정 사용자 그룹(Role)에게만 접근 권한 부여 가능 인스타그램 클론 미션 회고풀스택 스터디 4주차 미션은 강의에서 진행하는 Next.js와 Supabase Realtime Database, Authentication, RLS를 활용한 인스타그램 클론 앱에 여러 편의 기능을 추가하는 것과, 지금까지 진행했던 모든 프로젝트들을 배포까지 해보는 것이었습니다. 이번에도 역시 강의와는 다르게 실제 인스타그램의 MVP를 구현하는 것을 목적으로 했으며, 4주간 학습했던 내용들을 최대한 담아보았습니다. 진행했던 미션은 해당 링크에서 보실 수 있습니다. 미션 수행 내용아래는 제가 수행한 미션에 대한 내용을 정리해보았습니다. 로그인 기능 이메일 기반 로그인Confirmation URL 방식프로필 설정아이디 설정 기능이름 설정 기능프로필 이미지 설정 기능유저 탐색 기능 유저 프로필 조회 기능팔로잉 수 조회 기능팔로워 수 조회 기능팔로잉 기능 팔로잉 목록 조회 기능팔로워 목록 조회 기능실시간 채팅 기능 메시지 삭제 기능메시지 읽음 상태 확인 기능채팅방 접속 상태 확인 기능 사용 기술프레임워크: Next.js v15, React v19데이터베이스: Supabase서버 상태관리: Tanstack Query v5클라이언트 상태관리: Zustand v5스타일 프레임워크: TailWindCSS v3, Mantine v7, Tabler Icons모노레포: Turbo Repo패키지 매니저: pnpm 트러블 슈팅 Next.js useSearchParams 훅 빌드 환경 에러개발환경에서는 다른 문제가 없었는데, 빌드 시점에 Next.js 에서 제공하는 useSearchParams 훅을 사용한 로직이 아래의 에러를 반환하는 문제가 있었습니다. usesearchparams() should be wrapped in a suspense boundary at page "/404". 관련 이슈를 찾아보니 Next.js 14 버전 부터 useSearchParams 훅이 기본적으로 suspense boundary 에서 동작하도록 수정되어서 발생하는 이슈였습니다. 저의 경우에는 탐색 페이지의 검색어, 팔로잉 페이지의 탭메뉴, 리다이렉트 시 토스트 메시지 노출 로직에 쿼리 파라미터를 사용했기에 useSearchParams 훅을 사용한 부분을 모두 아래와 같이 Suspense 로 감싸주어 해결하였습니다. const FollowingPage: React.FC = () => ( )const SearchPage: React.FC = () => ( )export const Provider: React.FC = ({ children }) => ( {children} ) Module Federation (Micro FrontEnd)모노레포로 모든 프로젝트를 구성했기에 마지막 배포 미션에서 4개의 프로젝트를 모두 배포하는 대신 1개의 프로젝트만 관리할 목적으로 마이크로 프론트엔드 기술을 적용해 보고 싶었습니다. 빌드 시점에 4개의 프로젝트를 하나의 앱으로 통합하여 하나의 도메인을 공유하는 상태로 엔드포인트만 바뀌는 방식으로 배포해보고 싶었는데, 마이크로 프론트엔드를 구성하는 핵심 기술인 Module Federation의 Next.js 플러그인이 App Router는 지원하지 않더라구요. 저는 이미 App Router로 모든 로직을 작성한데다 지금 시점에서 모두 Pages Router로 포팅하기에는 시간이 부족하다 판단하여 해당 방식은 포기하였습니다. 그래서 대체재로 Wrapper 형식의 앱을 추가로 생성한 후 Next.js의 Multi Zones 기능과 Middleware, Rewrites 기능을 조합하여 4개의 프로젝트를 묶는 방식을 고려해봤었는데, 해당 방식은 결국 4개의 프로젝트를 개별로 배포해야하는 문제가 여전히 존재하여 결국 포기하게 되었습니다. 아무리 찾아봐도 마땅한 대안이 없더라구요. 해당 내용은 직접적인 트러블 슈팅은 아니지만 아쉬워서 남겨보았습니다. 후기처음 OT 때도 코치님이 말씀해주셨지만, 이번 주차에서는 미션의 난의도가 확 올라갔던 것 같습니다. 저는 특히 테이블 설계가 어려웠던 것 같아요. 기본적인 테이블들을 정규화를 거쳐 분리해놓으니 각 기능 별로 JOIN 로직을 거치는 단계가 복잡해져서 API 로직을 작성하면서도 이게 맞나 라는 생각이 들었던 적이 많았던 것 같습니다. 백엔드와 작업할 때 상황별로 필요한 인터페이스를 하나의 API에 모아 달라고 요청하는 경우가 많았었는데, 그게 백엔드 입장에서는 생각보다 쉽지 않은 일일 수 있었겠구나 라는 생각도 하게 되었습니다. 이번 4주차 미션을 끝으로 워밍업 클럽을 완주하게 되었는데, 개인적으로 풀스택을 지향하기에 앞으로 백엔드를 더 공부해 볼 계획이라 이번 경험이 앞으로의 학습에 많은 도움이 될 것 같아요. 이번 스터디의 핵심인 Supabase는 한달 동안 사용해보니 커스터마이징 하고 싶은 부분이 몇가지 있어 Supabase 하나로 기존 백엔드의 역할을 모두 대체하는 것은 한계가 있다고 느껴졌지만, 백엔드를 모르는 프론트엔드 개발자도 손쉽게 하나의 서비스를 온전히 구현할 수 있을 정도로, 쉽고 강력하다는 장점이 크기에 앞으로도 간단한 MVP를 구현해볼 일이 있다면 종종 사용해 볼 것 같아요. 풀스택 3기 러너분들 모두 수고 많으셨습니다! 긴글 읽어주셔서 감사합니다. ☺
프론트엔드
・
워밍업클럽3기
・
풀스택
・
Next.js
・
Supabase
2025. 03. 23.
0
[인프런 워밍업 클럽 3기] 풀스택 스터디 3주차 미션 회고 발자국
학습 내용 요약인프런 워밍업 클럽 3기 풀스택 스터디 3주차에는 Supabase Database를 활용하여 페이지네이션을 다루는 방법을 학습하였습니다. 인프런에서 발자국을 작성할 때 강의를 보지 않고도 강의 내용을 파악할 수 있을 만큼 자세한 내용을 작성하는 건 지양해 달라고 가이드한 만큼 강의에 대한 필기는 최소화 하고 회고 위주로 적어보겠습니다. Supabase Client Query Builderlet { data: movie, error } = await supabase .from('movie') // 'movie' 테이블에서 데이터를 가져옴 .select("*") // 모든 컬럼을 선택 // 필터 조건 (WHERE 절과 유사) .eq('column', 'Equal to') // column이 'Equal to' 값과 같은 경우 .gt('column', 'Greater than') // column이 지정 값보다 큰 경우 (>) .lt('column', 'Less than') // column이 지정 값보다 작은 경우 ( Supabase에서 Text와 Varchar 이해하기요약두 유형 모두 문자열을 저장하는 목적으로 사용됨저장 방식과 성능에 대한 차이가 있지만, Supabase는 단순성을 강조하므로, 특별한 이유가 없다면 기본적으로 text를 사용하는 것이 권장됨 Text긴 문자열을 저장하는 데 사용됨문자 개수에 대한 제한이 없으며, 길이를 예측하기 어려운 문자열에 적합함VarcharVariable Character Length(가변 길이 문자)의 약어최대 길이를 설정할 수 있음 → 데이터 일관성을 유지하는 데 유용하며, 특정 쿼리에서 성능이 약간 향상될 수 있음 페이지네이션 구현 방식: Offset vs CursorOffset Based Pagination 동작 방식OFFSET과 LIMIT을 사용해 특정 범위의 데이터를 가져옴장점특정 페이지로 바로 이동 가능 (예: 1페이지, 5페이지 등)직관적이고 구현이 간단함단점데이터가 많아질수록 OFFSET 성능 저하 (큰 OFFSET 값일수록 느려짐)데이터가 변경되면 순서가 달라질 수 있어 불안정함Cursor Based Pagination동작 방식마지막 항목의 특정 필드(예: created_at 또는 id)를 커서로 사용해 이후 데이터를 가져옴 장점성능이 우수함 (특히 큰 데이터셋에서 OFFSET 사용 없이 빠르게 조회 가능).데이터가 변경되더라도 안정적인 페이지네이션이 가능함.단점특정 페이지로 바로 이동이 어렵고, 이전 페이지로 돌아가는 것이 복잡할 수 있음.구현이 상대적으로 복잡함. Netflix 클론 미션 회고풀스택 스터디 3주차 미션은 강의에서 진행하는 Next.js와 Supabase Database를 활용한 Netflix 클론 앱에 찜 기능을 추가하는 것이었습니다. 진행했던 미션은 해당 링크에서 보실 수 있습니다. 미션 수행 내용아래는 제가 수행한 미션에 대한 내용을 정리해보았습니다. 영화 목록 조회 기능찜 리스트 조회 기능키워드 검색 기능무한 스크롤 지원영화 상세 정보 조회 기능동적 메타데이터 지원SSR 지원영화 찜하기 기능낙관적 업데이트 지원 사용 기술프레임워크: Next.js v15, React v19데이터베이스: Supabase서버 상태관리: Tanstack Query v5클라이언트 상태관리: Zustand v5스타일 프레임워크: TailWindCSS v3, Mantine v7, Tabler Icons모노레포: Turbo Repo패키지 매니저: pnpm 페이지네이션강의에서는 옵셋 기반으로 페이지네이션을 구현하였지만, 저는 커서 기반 페이지네이션이 데이터 추가 또는 삭제 시 페이지 별 인덱스가 꼬여 다른 페이지에 같은 데이터가 존재하거나 데이터를 건너뛸 수 있는 문제가 없기에 무한스크롤 기능에 조금 더 적합하다고 판단하여 커서 기반으로 페이지네이션을 구현하였습니다.export interface SearchMoviesParams { keyword?: string cursor?: number | null size?: number like?: boolean } export interface SearchMovies { (params: SearchMoviesParams): Promise } export const searchMovies: SearchMovies = async ({ cursor = null, keyword = '', like = false, size = 12, }: SearchMoviesParams) => { const client = await createServerSupabaseClient() const first = !cursor let query = client.from('movie').select('*').order('id', { ascending: true }) if (keyword) { query = query.ilike('title', `%${keyword}%`) } if (like === true) { query = query.eq('is_like', true) } if (cursor) { query = query.gt('id', cursor) } const { data, error } = await query.limit(size + 1) if (error) { console.log(error) return { data: [], nextCursor: null, first, last: true, error } } const hasNextPage = data.length > size const nextCursor = hasNextPage ? (data[size - 1]?.id ?? null) : null return { data: ( data?.map((movie) => ({ id: movie.id, title: movie.title, imageURL: movie.image_url, overview: movie.overview, popularity: movie.popularity, releaseDate: movie.release_date, voteAverage: movie.vote_average, isLike: movie.is_like, })) ?? [] ).slice(0, size), nextCursor, first, last: !hasNextPage, } } 찜 기능 테이블 스키마아래는 3주차 미션인 찜 기능을 구현할 때 작성한 movie 테이블 스키마입니다.CREATE TABLE movie ( id SERIAL PRIMARY KEY, image_url TEXT NOT NULL, title TEXT NOT NULL, overview TEXT NOT NULL, vote_average FLOAT8 NOT NULL, popularity FLOAT8 NOT NULL, release_date DATE NOT NULL, is_like BOOLEAN NOT NULL DEFAULT FALSE );해당 프로젝트에서는 별도로 회원 관리를 하지 않기 때문에 간단하게 컬럼을 하나 추가하여 구현하였지만, 만약 회원이 존재하는 상황이라면 테이블을 따로 분리한 후 회원 id와 영화 id를 받아와서 왜래키(FK)로 관리하는 방식도 괜찮았을 것 같습니다. 후기이번 주차에는 백엔드의 대표적인 작업 중 하나인 페이지네이션을 학습해 볼 수 있었습니다. 강의와는 다르게 Supabase를 사용한 커서 기반 페이지네이션을 구현해 보았는데, 공식 문서를 읽는 것이 쉽지 않았던 것 같습니다. 이전 주차들에서도 동일하게 Supabase 클라이언트에서 제공하는 쿼리 빌더 메서드들이 무슨 역할을 하는지 정리 해야겠다고 생각했는데 이번 주차에 드디어 하게 되었네요. 이제 다음 주차 미션인 인스타그램 클론까지 학습하면 간단한 MVP는 혼자서 구현해 볼 수 있을 것 같아서 기대됩니다. 긴글 읽어주셔서 감사합니다. ☺
프론트엔드
・
워밍업클럽3기
・
풀스택
・
Next.js
・
Supabase
2025. 03. 16.
1
[인프런 워밍업 클럽 3기] 풀스택 스터디 2주차 미션 회고 발자국
학습 내용 요약 인프런 워밍업 클럽 3기 풀스택 스터디 2주차에는 Supabase Storage를 활용하는 방법을 다루었습니다. 인프런에서 발자국을 작성할 때 강의를 보지 않고도 강의 내용을 파악할 수 있을 만큼 자세한 내용을 작성하는 건 지양해 달라고 가이드한 만큼 강의에 대한 필기는 최소화 하고 회고 위주로 적어보겠습니다. Supabase Strorage 기능 간단 정리Supabase의 Storage 기능은 파일 저장을 위한 서비스로, AWS S3와 같은 오브젝트 스토리지 기능을 제공함. Next.js, React, Flutter, Node.js 등 다양한 환경에서 사용할 수 있으며, PostgreSQL 기반의 권한 관리(RLS)를 지원하는 것이 특징임. 1. 파일 저장 및 관리이미지, 동영상, 문서 등 다양한 파일 형식을 저장 가능파일 업로드, 다운로드, 삭제, 이동 등의 기능 제공버킷(Bucket) 단위로 파일을 관리2. 권한 및 보안 (RLS)Row Level Security (RLS): PostgreSQL과 동일한 방식으로 접근 권한을 설정 가능공개(Public) 및 비공개(Private) 버킷 지원JWT 기반 인증을 사용하여 사용자별 접근 제한 가능3. 파일 접근 방식퍼블릭 파일: 누구나 URL을 통해 접근 가능프라이빗 파일: 인증된 사용자만 접근 가능 (Signed URL 활용)서명된 URL (Signed URLs): 일정 시간 동안만 유효한 URL 생성 가능4. 폴더 및 파일 구조디렉토리(폴더) 개념 지원폴더 내에서 파일 정리 및 관리 가능5. API 및 SDK 지원Supabase Client SDK를 통해 간편한 파일 업로드 및 관리 가능RESTful API 제공 (HTTP Client를 사용하여 직접 호출 가능)6. 이미지 변환 및 최적화Supabase Storage Image Transformation 지원 (이미지 크기 조절, 포맷 변경 등)CDN을 통해 빠른 이미지 제공 가능 Dropbox 클론 미션 회고 풀스택 스터디 2주차 미션은 강의에서 진행하는 Next.js와 Supabase Storage를 활용한 Dropbox 클론 앱에 사진 별 마지막 수정 시간을 표시하는 것이었지만, 저는 기타 편의기능을 더 추가해 보았습니다. 진행했던 미션은 해당 링크에서 보실 수 있습니다. 미션 수행 내용아래는 제가 수행한 미션에 대한 내용을 정리해보았습니다. 기능 명세이미지 파일 업로드 기능드래그 앤 드롭 기능다중 업로드 기능업로드한 이미지 파일 조회 기능키워드 검색 기능이미지 파일 다운로드 기능업로드한 이미지 파일 수정 기능이미지 파일명 변경 기능업로드한 이미지 파일 삭제 기능 강의에서는 기본적으로 파일 입출력에 관련된 기능만 다루었지만, 저는 실제 사용성을 고려하여 파일 업로드 전 미리보기 기능, 이미지 다운로드 기능, 파일 이름 변경 기능 등을 추가해 보았습니다. 또한 강의에서는 react-dropzone 라이브러리를 사용해서 드래그 앤 드롭 기능을 구현하였지만, 저는 프로젝트의 기본적인 스타일 프레임워크로 만타인을 사용하고 있었기 때문에, 만타인에서 따로 지원하는 @mantine/dropzone 라이브러리를 사용하여 구현하였습니다. 미션에 사용한 기술들은 아래에 따로 정리해 두었습니다. 사용할 때마다 느끼는 거지만 만타인은 정말 편한 것 같아요. 사용 기술프레임워크: Next.js v15, React v19데이터베이스: Supabase서버 상태관리: Tanstack Query v5클라이언트 상태관리: Zustand v5스타일 프레임워크: TailWindCSS v3, Mantine v7, Tabler Icons모노레포: Turbo Repo패키지 매니저: pnpm 트러블 슈팅아래는 미션을 진행하면서 만났던 문제들을 해결하는 과정에 대한 내용입니다. 파일명에 한글이 포함될 경우 Supabase Storage에 업로드하지 못하는 문제이미지 파일 이름에 한글이 포함될 경우 업로드가 되지 않는 문제가 있었습니다. 이슈를 찾아보니 Supabase Storage의 정책적인 문제였고, AWS의 S3 서비스도 동일한 문제를 가지고 있었기에 아래 조치들을 취하였습니다. 조치 1.처음 취했던 조치는 아래와 같이 nanoid 라이브러리를 활용하여 중복되지 않는 이름을 생성 후 기존의 파일 이름을 대체하는 방식을 사용했었습니다. 하지만 해당 방식을 사용하면 기존의 파일 이름이 사용자가 식별하지 못하는 값으로 대체되는 문제와, 중복되는 파일을 확인할 수 있는 방법이 없어지는 문제가 있어 최종적으로는 사용하지 않았습니다.'use server' import { nanoId } from 'nanoid' export const uploadImages = async ({ files, }: UploadImagesParams): Promise => { const client = await createServerSupabaseClient() return await Promise.all( files.map((file) => { const extension = extractExtension(file.name) const path = `/${nanoId() + '.' + extension}` return client.storage .from(process.env.SUPABASE_BUCKET_NAME!) .upload(path, file, { upsert: true }) }), ) } 조치 2.두 번째로 취한 조치는 조금 번거롭긴 하지만 파일과 1 대 1 로 대응되는 데이터베이스 테이블을 만들어서 관리하는 방식을 사용하였습니다. Supabase에서 지원하는 uuid를 활용하여 테이블의 Primary Key를 설정해 주었고, 이미지 업로드 시 먼저 테이블에 기존 파일 이름을 기반한 데이터 insert 후 생성된 uuid를 사용하여 파일명을 재설정하는 방식으로 우회하였습니다. Supabase에서 지원하는 uuid를 사용했기에 nanoid 같은 별도의 식별자 생성 라이브러리를 관리하지 않을 수 있었습니다.export const uploadImages = async ({ files, }: UploadImagesParams): Promise => { const client = await createServerSupabaseClient() const databaseQueries = files.flatMap((file) => { return client .from('minibox') .upsert({ name: file.name }) .select() .then((result) => result.data?.[0] ?? null) }) const targetFiles = await Promise.all(databaseQueries) const storageQueries = targetFiles.map((data) => { if (!data) { return { data: null } } const extension = extractExtension(data.name) const path = `/${data.id + '.' + extension}` const file = files.find((file) => file.name === data.name)! return client.storage .from(process.env.SUPABASE_BUCKET_NAME!) .upload(path, file, { upsert: false }) }) return await Promise.all(storageQueries) }업로드한 파일명이 한글일 경우 올바르게 검색 되지 않는 문제 (feat. MacOS)MacOS 환경에서 업로드한 파일을 별도의 후처리 없이 그대로 데이터베이스에 업로드 했더니 한글이 포함된 파일명에 대해서 아래와 같이 문자열 포함 여부를 판단하는 ilike 쿼리가 제대로 동작하지 않는 문제가 있었습니다.export const getImages = async ({ query = '' }: GetImagesParams): Promise => { const client = await createServerSupabaseClient() const imagesDataAll = await client.from('minibox').select('*').ilike('name', `%${query}}%`) } 원인을 분석해보니 아래와 같이 파일 이름에 한글이 포함되어 있을 시 자음과 모음이 모두 분리된 상태로 저장되어 있어 특정 키워드 포함 여부를 올바르게 판단하지 못해 발생한 문제였습니다.// input 'temp-훈이머리귤.jpeg'.split('')조치기존에 사용하던 ilike 쿼리를 제거하고, 자바스크립트에서 지원하는 String.prototype.normalize 메서드를 사용하여 기존 데이터에 대한 정규형 정준 결합(Normalization Form Canonical Composition) 절차를 거친 후 필터링을 거치는 방법으로 해결하였습니다.export const getImages = async ({ query = '' }: GetImagesParams): Promise => { const client = await createServerSupabaseClient() const imagesDataAll = await client.from('minibox').select('*') const targetData = imagesDataAll.data .filter(({ name }) => name.normalize('NFC').includes(query)) .map(({ id, name }) => `${id + '.' + extractExtension(name)}`) } 후기저는 이제껏 프론트엔드 개발을 접해보면서 한 번도 이미지 파일에 관련된 기능을 작업해보지 않았었습니다. 물론 서버 액션을 사용해서 조금 더 간략한 인터페이스를 사용했기에 실제 FormData 인터페이스를 사용하여 통신 로직을 작성하는 경험은 해보지 못했다는 한계가 있지만, 이번 미션을 통해 자바스크립트로 이미지 파일을 핸들링하는 방법과, Storage 서비스를 연동하여 저장까지 모두 접해볼 수 있어서 개인적으로는 만족스러웠던 한 주였던 것 같습니다. 긴글 읽어주셔서 감사합니다. ☺
프론트엔드
・
워밍업클럽3기
・
풀스택
・
Next.js
・
Supabase
2025. 03. 09.
0
[인프런 워밍업 클럽 3기] 풀스택 스터디 1주차 미션 회고 발자국
학습 내용 요약인프런 워밍업 클럽 3기 풀스택 스터디 1주차에는 해당 강의에서 사용될 라이브러리 및 프레임워크에 대한 소개와 사용법 위주로 다루었습니다. 인프런에서 발자국을 작성할 때 강의를 보지 않고도 강의 내용을 파악할 수 있을 만큼 자세한 내용을 작성하는 건 지양해 달라고 가이드한 만큼 강의에 대한 필기는 최소화 하고 회고 위주로 적어보겠습니다. 강의의 목적인 Next.js와 Supabase를 사용해서 서비스를 만들어보는 것에 집중하여 스타일링이나 기타 상태관리 라이브러리에 대한 내용은 작성하지 않았습니다. Next.js 간단 정리Next.js란?개인이 풀스택 개발을 하기에 최적화 된 웹 프레임워크서버사이드 렌더링이 큰 특징이며, React와 비슷한 문법으로 더 현대적인 웹 서비스를 구현할 수 있음서버에서부터 HTML을 최적화해서 웹으로 내려주기 때문에 SEO에 도움이 됨자체적으로 API 구축이 가능하기 때문에 사이즈가 크지않은 개인 프로젝트 정도의 규모라면 별도로 서버구축을 할 필요 없이 Next.js만으로 서버 구축까지 전부 가능함Server Actions서버에서 실행되는 비동기 함수 생성 기능"use server" 지시어를 파일 최상단에 선언하면 해당 파일 내 모든 함수가 Server Action으로 간주됨서버 및 클라이언트 컴포넌트에서 호출 가능주로 폼 제출 및 데이터 변경(뮤테이션)에 사용내부적으로 POST 요청을 사용하며, 인자 및 반환값은 React에서 직렬화 가능해야 함 Supabase 간단 정리Supabase 란?PostgreSQL 기반의 오픈소스 백엔드 서비스로, 개발자들이 손쉽게 데이터베이스, 인증, 스토리지, 서버리스 함수 등을 활용할 수 있도록 지원하는 플랫폼상용 서비스인 Firebase의 대안으로 자주 언급됨Supabase 장점오픈소스 프로젝트 (자체 서버 구축 가능)PostgreSQL 기반 (관계형 DB 장점을 살릴 수 있다)Firebase 대비 저렴다양한 연동 방식 지원 (+ GraphQL, API, SDK, DB Connection)Supabase 단점아직 성숙하지 않은 커뮤니티 기반비교적 적은 기능들, 적은 서비스 연동 지원부족한 문서화, 한글 문서 부족Firebase보다 높은 러닝커브 Todo List 미션 회고풀스택 스터디 1주차 미션은 강의에서 진행하는 Next.js와 Supabase를 활용한 Todo List 앱에 기능을 더 추가해보는 것이었습니다. 진행했던 미션은 해당 링크에서 보실 수 있습니다. 미션 수행 내용아래는 제가 수행한 미션에 대한 내용과 함께 고민했던 점과 아쉬웠던 점을 정리해보았습니다. 기능 명세할 일 목록 조회 기능키워드 검색 기능할 일 수정 기능내용 변경 기능완료 여부 변경 기능할 일 삭제 기능정리해보면 위의 기능을 Next.js와 Supabase를 활용하여 구현하는 것이었습니다. 강의에서는 Material Tailwind 라이브러리를 사용했지만 저는 기존에 즐겨썼었던 TailWindCSS와 최근 관심가지게된 Mantine 라이브러리를 사용하여 UI를 구축하였고, 4주차 모두 동일한 환경으로 진행되는 것 같아 Turbo Repo와 pnpm workspace를 활용한 모노레포 환경을 구축해두고 프리셋을 관리하였습니다. 상세한 내용은 아래에 정리해두었습니다. 사용 기술프레임워크: Next.js v15, React v19데이터베이스: Supabase서버 상태관리: Tanstack Query v5클라이언트 상태관리: Zustand v5스타일 프레임워크: TailWindCSS v3, Mantine v7모노레포: Turbo Repo패키지 매니저: pnpm 강의에서는 TypeScript를 사용했지만 별도의 인터페이스를 작성하지는 않았는데, 저의 최근 관심사가 환경(라이브러리) 변화에 유연한 도메인 로직 작성하기여서 저는 아래와 같이 별도로 인터페이스를 작성 후 서버 액션을 사용하는 로직을 인터페이스에 맞추어 작성해 보았습니다. export interface Todo { id: number title: string createdAt: string updatedAt: string completed: boolean completedAt: string | null }export interface GetTodoListParams { query?: string } export interface GetTodoList { (params: GetTodoListParams): Promise }export type CreateTodoParams = Pick export interface CreateTodo { (params: CreateTodoParams): Promise }export type UpdateTodoParams = Pick export interface UpdateTodo { (params: UpdateTodoParams): Promise }export type DeleteTodoParams = Pick export interface DeleteTodo { (params: DeleteTodoParams): Promise } 기존에는 서버 액션에 대한 이해가 부족했어서 항상 Next.js를 사용하며 Route Handler로 별도의 API를 만들어서 작업했었는데 별도의 API 엔드포인트를 관리하지 않고 서버 액션으로 모두 구축해볼 수 있었습니다. 이번에는 강의에서 진행한 것과 같이 GET 요청에도 서버 액션을 사용했었는데 개인적으로 관련 내용에 대한 고민을 조금 했었습니다. Next.js의 공식 문서에서는 일반적으로 서버 액션을 데이터 변경(뮤테이션) 로직에만 사용하는 것으로 소개되었기도 했고, 기본적으로 POST 메서드를 사용한다고 했었는데, 이렇게 GET, PUT, DELETE 메서드를 사용해야하는 로직도 모두 POST로 통합해도 되는가라는 의문이 하나 있었고, GET 요청에도 서버 액션을 사용하니 생긴 문제인데 Next.js에서의 캐싱 전략을 어떻게 사용할 수 있을까에 대한 의문이 있었습니다. 강사님께서 제시하는 생산성 높은 풀스택 개발 방법론은 Supabase로 백엔드 필요 기능을 쉽고 빠르게 구축하고 Next.js의 서버 액션을 활용하여 별도의 API를 관리하는 절차를 건너뛰는 것이라고 이해했습니다. 저도 1인 풀스택 개발에 뜻이 있는 입장에서 되게 괜찮은 방법론이라 생각했는데, 해당 의문은 이로인해 근본적으로 서버 액션이라는 기술의 활용 범위를 넓힘으로 인해 발생했다고 생각합니다. 첫 번째 의문은 Next.js의 특수성으로 미루어 보아 문제가 없다고 결론지었습니다. 서버 액션이라는 개념 자체가 일반적으로 REST API를 설계하는데 사용되는 개념이 아니기도 하고, 애초에 엔드포인트 자체도 루트 경로로 받기 때문에 의미가 없는 것이죠. 프레임워크가 관리해주기 때문에 메서드와 엔드포인트로 작업 단위를 표현하지 않아도 괜찮다고 생각했습니다. 두 번째는 의문이라기 보다는 문제였죠. 저는 해당 문제를 해결하기 위해 Tanstack Query를 사용했습니다만 근본적인 해결은 아니었습니다. HydrationBoundary 와 prefetchQuery를 활용하여 SSR과 서버 컴포넌트 환경을 통합하였고, useQuery 훅과 Query Key Factor 방식을 사용하여 캐싱 전략 관리를 시도했는데, 다 만들고 보니 이건 클라이언트에 해당하는 캐싱 전략이었어요. 서버 쪽에서는 적절한 캐싱 전략이 없었습니다. 해당 문제는 공식 문서를 찾아보니 라우트 세그먼트의 설정을 상속받아 maxDuration 같은 설정을 따른다고 안내되고 있었습니다. 적용해보지는 못했지만 page.tsx에서 결정할 수 있는 것 같아 아마 다음 미션에서 기회가 된다면 사용해볼 예정입니다. 벌써 다음 미션이 기대되네요. 긴글 읽어주셔서 감사합니다. ☺
프론트엔드
・
워밍업클럽3기
・
풀스택
・
Next.js
・
Supabase