PromleeBlog
sitemapaboutMe

posting thumbnail
Supabase에서 Nextjs로 데이터 fetching하기(CSR/SSR)
Data fetching from supabase to Nextjs(CSR/SSR)

📅

🚀

들어가기 전에🔗

Supabase에서 Next.js로 데이터를 가져오는 방법은 크게 두 가지가 있다.
  1. 자체 엔드포인트 API
    를 만들어서 데이터를 가져오는 방법 + Prisma
  2. Supabase의
    REST API
    를 사용하는 방법
각각의 장단점을 비교해보면 다음과 같다.
구분장점단점
자체 엔드포인트 API- ORM을 사용할 수 있어서 DB를 쉽게 다룰 수 있다.
- DB에 직접 접근할 수 있어서 데이터를 더 효율적으로 가져올 수 있다.
- API를 직접 만들어야 하므로 시간이 더 걸린다.
- DB에 직접 접근하므로 보안에 취약할 수 있다.
- 많이 복잡한 편이다
Supabase의 REST API- DB에 직접 접근하지 않아도 된다.
- 코드가 매우 간결하다.
- 데이터를 가져오는데 제약이 있을 수 있다.
- 데이터를 가져오는데 시간이 더 걸릴 수 있다.
요약하자면 직관적이고 간결하게 데이터를 가져오고 싶다면
Supabase의 REST API
를 사용하는 것이,
데이터를 더 효율적으로 다루고 싶다면
자체 엔드포인트 API
를 만들어서 데이터를 가져오는 것이 좋다.
나는 두 방법을 혼용하여 이 블로그를 제작하였으며 그 방법을 소개하겠다.

🚀

자체 엔드포인트 API 사용🔗

Prisma 설치🔗

npm i -D prisma
npm i @prisma/client
이제 npx 로 prisma를 실행할 수 있다.
Next.js에서 Prisma를 사용하기 위해 초기 설정이 필요하다.
npx prisma init
init 명령어를 실행하면 prisma 폴더가 생성되며, 이 폴더 안에 schema.prisma 파일이 생성된다.
또한 .env 파일이 생성되는데, 여기에 DB_URL을 설정해주어야 한다.
Supabase 본인 프로젝트 > Settings > Configurations > Database > Connection string 에서 확인할 수 있다.
image
[YOUR-PASSWORD]는 본인의 프로젝트 비밀번호로 바꿔주어야 한다.
뒤에 붙어있는 :[포트번호]/postgress 는 지워주도록 하자.
./.env
DATABASE_URL="postgres://postgres.[Reference ID]:[YOUR-PASSWORD]@aws-0-ap-northeast-2.pooler.supabase.com
DIRECT_URL은 Prisma 클라이언트를 사용할 때 필요한 환경변수이다.
추가적으로 Next.js 프로젝트를 배포한다면 빌드 시에도 Prisma 클라이언트를 사용할 수 있도록 명령어를 추가해주어야 한다.
./package.json
{
  "scripts": {
		// ...
    "postinstall": "prisma generate"
	}
}

DB 스키마 가져오기🔗

Prisma를 사용하면 DB 스키마를 가져올 수 있다.
npx prisma db pull
이 명령어를 실행하면 prisma/schema.prisma 파일에 DB 스키마가 자동으로 추가된다.
./prisma/schema.prisma (예)
generator client {
  provider = "prisma-client-js"
}
 
datasource db {
  provider  = "postgresql"
  url       = env("DATABASE_URL")
  directUrl = env("DIRECT_URL")
}
 
model Tag { // 추가
  id       BigInt     @id @unique @default(autoincrement())
  name     String     @default("")
}
추가된 스키마로 Prisma 클라이언트를 생성해보자.
npx prisma generate

DB 스키마 내보내기🔗

DB 스키마를 수정했다면, 이를 DB에 적용하기 위해 내보내기를 해주어야 한다.
npx prisma db push
수정된 스키마로 Prisma 클라이언트를 다시 생성해주자.
npx prisma generate

Api 엔드포인트 생성🔗

Prisma 클라이언트를 사용하여 데이터를 가져오는 API 엔드포인트를 만들어보자.
./src/app/api/tag/route.ts
const { PrismaClient } = require("@prisma/client");
import { NextResponse, NextRequest } from "next/server";
 
const prisma = new PrismaClient();
 
export async function GET() {
  try {
    return NextResponse.json( {data: await getTags()}, { status: 200 });
  } catch (error) {
    return NextResponse.json(
      { error: error || "Tags not Found" },
      { status: 405 },
    );
  }
}
 
async function getTags() {
  const Tags = await prisma.tag.findMany({
    orderBy: {
      id: "asc",
    },
  });
  return Tags;
}
Prisma 데이터 필터링, 정렬, 페이징 등을 사용하려면 Prisma Docs를 참고하자.

데이터 가져오기🔗

이제 Next.js 페이지에서 API 엔드포인트를 사용하여 데이터를 가져올 수 있다.
${process.env.NEXT_PUBLIC_API_BASE_URL}.env.local 파일에 설정한 API URL이다.
디버그 모드에서는 http://localhost:3000으로 설정하고, 배포 시에는 https://[your-domain]으로 설정해주어야 한다.
./src/app/tag/page.ts - SSR
export const Page = async () => {
	let tags;
  try {
    tags = await fetch(
      `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/tag`,
      { next: { revalidate: 60 } },
    )
      .then((res) => res.json())
      .then((data) => data.data);
  } catch (e) {
    tags = [];
  }
	return (
		<div>
			{tags.map((tag) => (
				<div key={tag.id}>
					<h1>{tag.name}</h1>
				</div>
			))}
		</div>
	);
};
./src/app/tag/page.ts - CSR
"use client";
import { useState, useEffect } from "react";
 
export const Page = () => {
	const [tags, setTags] = useState([]);
	useEffect(() => {
		fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/api/tag`)
			.then((res) => res.json())
			.then((data) => setTags(data.data));
	}, []);
	return (
		<div>
			{tags.map((tag) => (
				<div key={tag.id}>
					<h1>{tag.name}</h1>
				</div>
			))}
		</div>
	);
};

🚀

Supabase REST API 사용🔗

Supabase 설치 및 기본설정🔗

Supabase REST API를 쉽게 사용할 수 있게 만들어주는 라이브러리를 설치해준다.
npm i @supabase/supabase-js @supabase/ssr
Supabase에 접근하기 위해 프로젝트의 환경변수를 설정해주어야 한다.
다음 Supabase 링크 중간에 3. Declare Supabase Environment Variables 섹션에서 쉽게 확인 가능하다.
image
./.env.local
NEXT_PUBLIC_SUPABASE_URL=https://[your-project-id].supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=[your-anon-key]

Supabase 클라이언트 생성🔗

SSR
에서 사용할 클라이언트와
CSR
에서 사용할 클라이언트를 구분하여 유틸 파일을 생성해주어야 한다.
./src/utils/serverclientSSR.ts - SSR
import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { cookies } from "next/headers";
 
export const createClient = () => {
  const cookieStore = cookies();
 
  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return cookieStore.get(name)?.value;
        },
        set(name: string, value: string, options: CookieOptions) {
          try {
            cookieStore.set({ name, value, ...options });
          } catch (error) {
          }
        },
        remove(name: string, options: CookieOptions) {
          try {
            cookieStore.set({ name, value: "", ...options });
          } catch (error) {
          }
        },
      },
    },
  );
};
./src/utils/serverclientCSR.ts - CSR
import { createClient } from "@supabase/supabase-js";
 
export const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);

데이터 가져오기🔗

Supabase의 REST API를 사용하여 데이터를 가져오는 방법은 간단하다.
./src/app/tag/page.ts - SSR
import { createClient } from "@/utils/serverclientSSR";
interface Tag { // 데이터의 타입 정의
	id: number;
	name: string;
}
 
const getData = async () => {
  const supabase = createClient();
  const { data }: { data: Tag[] } = await supabase
    .from("Tag") // 테이블 이름
    .select(); // 모든 열의 데이터 가져오기
  return data;
};
 
export const Page = async () => {
	const tags = await getData();
	return (
		<div>
			{tags.map((tag) => (
				<div key={tag.id}>
					<h1>{tag.name}</h1>
				</div>
			))}
		</div>
	);
};
./src/app/tag/page.ts - CSR
"use client";
import { supabase } from "@/utils/serverclientCSR";
import { useEffect, useState } from "react";
interface Tag { // 데이터의 타입 정의
	id: number;
	name: string;
}
 
export const Page = () => {
	const [tags, setTags] = useState<Tag[]>([]);
	useEffect(() => {
		const getData = async () => {
			const { data }: { data: Tag[] } = await supabase
				.from("Tag") // 테이블 이름
				.select(); // 모든 열의 데이터 가져오기
			setTags(data);
		};
		getData();
	}, []);
	return (
		<div>
			{tags.map((tag) => (
				<div key={tag.id}>
					<h1>{tag.name}</h1>
				</div>
			))}
		</div>
	);
};
필터링 메소드를 활용하는 방안은 Supabase - Fetch data를 참고하자.

🚀

문제 발생 및 해결🔗

.env 파일의 DATABASE_URL 설정이 OS에 종속되어 수정이 안되는 문제🔗

➡️

해결🔗

DATABASE_URL의 변수명을 바꾸어준다.
./.env
NEWNAME_DATABASE_URL="postgres://postgres.[Reference ID]:[YOUR-PASSWORD]@aws-0-ap-northeast-2.pooler.supabase.com"
./prisma/schema.prisma
datasource db {
	provider = "postgresql"
	url      = env("NEWNAME_DATABASE_URL")
}

Prisma generate 시 에러 발생🔗

Error: EPERM: operation not permitted, unlink "C:\\**\\node_modules\\.prisma\\client\\query-engine-windows.exe"
➡️

해결🔗

stackoverflow link
VSCode를 껐다가 키면 된다...

🚀

결론🔗

더 생각해보기 - 두 방법 중 어떤 방법이 더 빠를까?🔗

당연히
Supabase REST API
를 사용하는 것이 더 빠르고 효율적이라고 생각했다.
데이터를 가져올 때 두 번의 과정을 거치는
자체 엔드포인트 API
를 사용하는 것이 더 느릴 것이라고 생각했다.
Server Action이 아닌 Client Side에서 같은 조건으로 비교해보았다.
하지만 결과를 보면
자체 엔드포인트 API
를 사용한 데이터가 사용자에게
더 빨리
보여지는 것을 확인할 수 있었다.
실제로 화면에 그려지는 시간은 체감될 정도였다.
image
links
자체 엔드포인트 API
를 사용한 데이터를 가져오는 시간을 측정,
Category ~
Supabase REST API
를 사용한 데이터를 가져오는 시간을 측정한 것이다.
캐시를 비활성화한 상태에서 여러 번 테스트해본 결과,
자체 엔드포인트 API
를 사용한 데이터가
평균 100ms 더 빨리
보여지는 것을 확인할 수 있었다.
가장 크게 영향을 미치는 요소는
Queuing
이었다.
데이터를 가져오는 데에 있어서 우선순위가 높은
자체 엔드포인트 API
를 사용한 데이터가 더 빨리 보여지는 것이다.
실제로
자체 엔드포인트 API
의 데이터가 모두 fetch된 후
Supabase REST API
의 데이터가 fetch된다.
자체 엔트포인트 API
를 제거한 상태에서도
Supabase REST API
의 큐잉 시간은 변하지 않았다.
이는 사용자 경험과 직결되기에 이를 해결할 방법을 찾기 전까지 나는
자체 엔드포인트 API
를 주로 사용할 듯 하다.

참고🔗