#TIL 1 모바일 청첩장 빌더 만들기 - supabase & NextJS(app router)

김병훈·2024년 2월 20일
0

bora-n-maria

목록 보기
1/7
post-thumbnail

supabase

one-to-one-relation

배경

template<->metadata, template<->wedding_hall이 one-to-many로 연결되어 있어서, template 데이터를 불러올 때 각 테이블에 해당하는 데이터가 배열로 들어오고 있었다.
one-to-one으로 연결할 수는 없을까? 검색하다가 다음 유튜브 영상을 보고 알게되었다.
one-to-one-relation

어떻게?

pk 값으로 참조를 하도록 함.

기존 metadata 테이블의 컬럼 구조 (one-to-many)

변경한 metadata 테이블의 컬럼 구조 (one-to-one)

App router (NextJS)

metadata

동적으로 metadata를 구성하기

page.tsx

type MetadataProps = {
  params: { id: string };
  searchParams: { [key: string]: string | string[] | undefined };
};

export async function generateMetadata(
  { params, searchParams }: MetadataProps,
  parent: ResolvingMetadata,
): Promise<Metadata> {
  // ...
  const res = await fetch(url);

  if (!res.ok) {
    return {
      title: "결혼식 청첩장",
      description: "결혼식에 초대합니다.",
    };
  }

  const body = await res.json();
  const instaTemplate = instaTemplateSchema.parse(body);

  return {
    title: instaTemplate.metadata.title,
    description: instaTemplate.metadata.description,
  };
}

favicon 만들기

chatGPT 한테, favicon 이미지 생성을 부탁했다. (만족)

Route API

as-is

  • page.tsx 에서 직접 supabase로 요청을 보내 데이터를 가져옴
  • page.tsx -> supabase

to-be

  • page.tsx 에서 Route API로 데이터 요청을 보내고, Router 에서 supabase로 요청을 보내 데이터를 가져옴
  • page.tsx -> route.ts -> supabase

왜 Route API를 사용했냐면!

  1. 서버 컴포넌트의 fetch/revalidate를 사용하기 위해
    supabase로 직접 연결하는 것은 내부적으로 어떻게 구성되어 있는지 확실히 모르겠음.
    캐싱을 활용하기 위해 fetch를 통해 route를 호출하려고 함
  2. 중복 로직 제거
    insta-template 데이터를 요청하는 페이지가 많아서, 각 페이지별로 supabase로 요청하는 로직을 가지고 있는 것은 비효율적이라고 생각했음.
  3. postman으로 API 테스트하기 위해서
    supabase에 직접 요청을 보내서 테스트를 하는 건, join 등이 복잡함.

막혔던 부분

  1. 라우트 API에서 supabase 클라이언트 생성하기 (해결)
    supabase의 클라이언트를 생성하는 createServerClient를 route.ts에서 호출하면 안될거라고 생각했음.
    supabase 공식 문서의 예제에서는 route에서 호출한 것이 아니고, server action 에서 호출했었기 때문에 cookieStore가 다른 값을 가질 것이라고 생각했던 것
    But, 라우트 API에서도 동일한 방식으로 구현이 가능했음
export const GET = async (req: NextRequest) => {
	const cookieStore = cookies();
  	const supabase = createClient(cookieStore);
  	const { data } = await supabase.from("template").select("*");
  	// ...
  
  	return NextResponse.json({ ...template });
}

cookies()의 반환값도 쿠키 정보를 가지고 있고,
req.cookies도 쿠키 정보를 가지고 있는데,
둘이 어떻게 다른건지는 좀 알아봐야 할 것 같다.

  1. 서버 컴포넌트(페이지)에서는 브라우저의 경로를 알 수 없어서, 상대경로로 fetch를 할 수 없었음
const Page = async (props) => {
  // ...
  const id = props.params.id;
  const res = await fetch(`/api/template/${id}`);
  // Internal error: TypeError: Failed to parse URL from /api/template/test1

  // ...
}

protocol과 host를 구하는 로직을 추가하여, 해결

protocol은 꼭 저렇게 되지 않을 수 있지만, 현재는 로컬만 동작하고 있기 때문에 일단 두기로 했음

import { headers } from "next/headers";

const Page = async (props) => {
	const id = props.params.id;
	const host = headers().get("host") || "localhost:3000";
  	const protocol = host.includes("localhost") ? "http" : "https";
  	const url = `${protocol}://${host}/api/templates/${id}`;
  	const res = await fetch(url);
  
  	// ...
}
profile
재밌는 걸 만드는 것을 좋아하는 메이커

0개의 댓글