DEV

여기를 눌러보세요!

Published on

liveblocks Overlay Comments 사용 후기

Authors
  • avatar
    Name
    Charles

liveblocks란?

liveblocks는 댓글, 공지, 텍스트 편집기 기능을 제공해주고 실시간 API를 활용해 협업을 할 수 있도록 도와주는 도구이다.

사용 이유

내가 liveblocksOverlay Comments를 사용하게 된 이유는 블로그에 댓글 기능을 추가하고 싶었는데 최초에는 내가 댓글 관련 DB 및 API를 설계 해서 진행하려고 하였으나 기존에 생각했던 대로 진행 하면 시간이 많이 소요 될 것 같았고, 빠른 시간에 댓글 기능을 추가해보고 싶기도 한 부분과 liveblocks의 기능을 예전에 vercel에서 Next.js 홈페이지에서 실시간으로 사이트를 사용하고 있는 사람들에게 댓글을 받고 서로 대화를 주고 받고 있는 부분에서 흥미를 느껴서 내 블로그에도 적용 해보고 싶다는 생각이 들어서 선택하게 되었다.

사용 방법

liveblocks 사이트에 접속해서 회원가입 또는 소셜 로그인을 통해서 로그인을 한다. liveblocks.io 로그인을 하면 Dashboard화면으로 이동하게 되는데 여기서 프로젝트 생성 버튼을 클릭해서 내가 사용할 프로젝트에 맞춰서 프로덕션용과 개발용 두가지를 활용해서 프로젝트를 생성 할 수 있다. dashboard create-project-1 create-project-2 나는 이 부분에서 liveblocks가 개발 편의성을 많이 주었다고 생각이 들었다.
먼저 Development로 프로젝트의 개발서버에서 사용할 기능을 만들어서 충분히 테스트를 거친 후에 Production을 만들어서 배포시에 해당 Secret key만 작성해주면 되었던 부분이 프로덕션용과 개발용을 분리해서 작업 할 수 있다는 부분에서 많이 편리 했던 것 같다.
프로잭트를 생성하고 Quickstart 탭으로 이동하면 아래와 같이 원하는 기능을 추가 할 수 있도록 단계가 제공되고 있다.
해당 부분에 맞춰서 진행을 해주면 손쉽게 프로젝트에 적용을 할 수 있다. QuickStart 나는 댓글기능을 추가하고 싶어서 Comments를 선택 Select your technology 그리고 현재 블로그는 Next.js로 개발되어있어서 Next.js를 선택
이후 아래에 있는 단계를 다 설정하고 나면 liveblocks에서 제공해주는 예제가 존재한다. 여기서 나는 Overlay Comments기능을 사용해보고 싶어서 해당 예제를 선택해서 프로젝트에 적용해보았다. Examples using Next.js

Nextjs Overlay Comments에 프로젝트에 사용되어야 하는 모든 코드들이 제공이 된다.

기능을 추가하면서 겪었던 문제점

LiveblocksProvider

<LiveblocksProvider authEndpoint="/api/liveblocks-auth" resolveUsers={async ({ userIds }) => { const searchParams = new URLSearchParams( userIds.map(userId => ['userIds', userId]), ); const response = await fetch(`/api/user-info?${searchParams}`); if (!response.ok) { throw new Error('Problem resolving users'); } const users = await response.json(); return users; }} resolveMentionSuggestions={async ({ text }) => { const response = await fetch( `/api/user-info/search?text=${encodeURIComponent(text)}`, ); if (!response.ok) { throw new Error('Problem resolving mention suggestions'); } const userIds = await response.json(); return userIds; }} > {children} </LiveblocksProvider>

liveblocks에는 Room이라는게 있다. 해당 부분은 문서, 댓글 등등 협업을 위해서 하나의 공간에서만 작업을 하기 위해 roomId를 통해서 각각의 공간이 분리가 되어있다. resolveUsers는 각 Room에 허용이 된 사람인지를 파악하는 부분이다.

resolveUsers={async ({ userIds }) => { const searchParams = new URLSearchParams( userIds.map(userId => ['userIds', userId]), ); const response = await fetch(`/api/user-info?${searchParams}`); if (!response.ok) { throw new Error('Problem resolving users'); } const users = await response.json(); return users; }}

예제에서는 dummy로 만들어진 database를 가지고 해당 부분이 작성이 되어있어서 실제 db랑 연결할 때 내가 착각을 해서 벌어진 문제였다.
예제랑 같이 먼저 liveblocks에서 이미 사용이 허용된 userIds를 배열에 해당 사용자의 id(내가 지정한 id)를 전달해준다.
전달 받은 아이디를 내 db의 전체 회원에서 조회한 뒤에 있는 회원을 전부 전달해주면 끝나는 문제였는데 나는 해당 부분을 로그인 된 사용자의 정보만 취합해서 보내주는 부분에서 문제가 발생했다.

처음에 잘 못 이해 했을 때 작성했던 API

export async function GET(request: NextRequest) { const cookieStore = cookies(); const supabase = supabaseServer(cookieStore); const { data: users, error } = await supabase.from('users').select('*'); const session = await getServerSession(authOptions); if (!users || !Array.isArray(users) || !session) { return new NextResponse('Missing or invalid userIds', { status: 400 }); } const { user: loginUser } = session; const blogUsers: Array<UserMeta> = users.map(user => ({ id: user.id, info: { name: user.name, avatar: user.image, color: getUserColor(user.id) }, })); const findInfo = blogUsers.find(blogUser => blogUser.id === loginUser.id)?.info || null; return NextResponse.json([findInfo], { status: 200 }); }

수정 된 API

export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); const userIds = searchParams.getAll('userIds'); const cookieStore = cookies(); const supabase = supabaseServer(cookieStore); const { data: users, error } = await supabase .from('users') .select('*') .returns<Array<User>>(); if (!users || !Array.isArray(users)) { return new NextResponse('Missing or invalid userIds', { status: 400 }); } return NextResponse.json( userIds.map(userId => getUser({ users, userId })?.info || null), { status: 200 }, ); }

예제 API

import { getUser } from "@/database"; import { NextRequest, NextResponse } from "next/server"; export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); const userIds = searchParams.getAll("userIds"); if (!userIds || !Array.isArray(userIds)) { return new NextResponse("Missing or invalid userIds", { status: 400 }); } console.log(userIds.map((userId) => getUser(userId)?.info || null)); return NextResponse.json( userIds.map((userId) => getUser(userId)?.info || null), { status: 200 } ); }

내가 착각 한 부분 때문에 댓글 작성 테스트를 하면서 사용자를 다르게 했음해도 아래와 같이 사용자가 다르게 표시가 되어야 했는데 로그인한 사용자로 모두가 표시되서 왜 그런지 한참을 코드를 다 비교해보면서 삽질(?)을 했었는데 내가 친절하게 작성해준 예제를 제대로 이해하지 못한 부분이 문제였다... Human Error가 제일 무섭다.....

comments

제공해주는 예제를 잘 이해하고, liveblocks의 Docs도 잘 참고해서 기능을 추가하는게 중요한 것 같다.
그래도 좀 해매긴했지만 해당 기능에 대해서 제대로 이해하는 기회가 되었다고 생각이 된다. 오랜만에 개발 집착이 발생하면서 해당 부분을 끝까지 포기하지 않고 제대로 추가 할 수 있었던 것 같다.

후기

내가 직접 설계한 댓글 기능은 아니었지만 새로운 프론트 기술을 적용해보면서 재미있었던 것 같다. 이번에도 배운 점이 library들은 잘 만들어져 있지만 그 기능을 사용하는 사람들이 어떻게 이해하느냐에 따라 완전 다른 방향으로 기능이 동작하는 부분을 보고 해당 기능을 만든 제작자의 의도를 잘 파악하고 설명서를 제대로 읽도록 해야 겠다고 생각했다.
liveblocks의 무료 사용 제한이 MAU(Monthly Active User)가 100명인거는 조금 적은거 같다.(물론 내 블로그 방문자가 그렇게 많지는 않지만)

다음 스탭

요즘 제일 핫한 AI를 활용한 프로젝트를 구상 중인데 일단 유튜브를 통해서 접한 몇가지 프로젝트를 일단 작업해보고 몇개를 믹스하는 방향으로 가볼까 한다. 계획을 잘 세워서 해당 기간을 잘 이용하자!